diff options
425 files changed, 11942 insertions, 5614 deletions
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 448ee6160ce0..d5c386fc5368 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -155,6 +155,7 @@ public class UserLifecycleTests { public void tearDown() throws Exception { setSystemProperty("debug.usercontroller.user_switch_timeout_ms", mUserSwitchTimeoutMs); mBroadcastWaiter.close(); + mUserSwitchWaiter.close(); for (int userId : mUsersToRemove) { try { mUm.removeUser(userId); @@ -207,10 +208,10 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); final int userId = createUserNoFlags(); - mRunner.resumeTiming(); - Log.i(TAG, "Starting timer"); - runThenWaitForBroadcasts(userId, () -> { + mRunner.resumeTiming(); + Log.i(TAG, "Starting timer"); + mIam.startUserInBackground(userId); }, Intent.ACTION_USER_STARTED); @@ -273,9 +274,7 @@ public class UserLifecycleTests { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); - runThenWaitForBroadcasts(testUser, () -> { - mAm.switchUser(testUser); - }, Intent.ACTION_USER_UNLOCKED); + switchUser(testUser); mRunner.pauseTiming(); Log.i(TAG, "Stopping timer"); @@ -362,10 +361,10 @@ public class UserLifecycleTests { }, Intent.ACTION_MEDIA_MOUNTED); mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> { - mRunner.resumeTiming(); - Log.i(TAG, "Starting timer"); - runThenWaitForBroadcasts(userId, () -> { + mRunner.resumeTiming(); + Log.i(TAG, "Starting timer"); + mAm.switchUser(startUser); }, Intent.ACTION_USER_STOPPED); diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java index 228d14c3a05b..82245977760b 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java @@ -17,61 +17,87 @@ package android.multiuser; import android.app.ActivityManager; +import android.app.IActivityManager; +import android.app.IUserSwitchObserver; import android.app.UserSwitchObserver; import android.os.RemoteException; import android.util.Log; import com.android.internal.util.FunctionalUtils; -import java.util.concurrent.CountDownLatch; +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -public class UserSwitchWaiter { +public class UserSwitchWaiter implements Closeable { private final String mTag; private final int mTimeoutInSecond; + private final IActivityManager mActivityManager; + private final IUserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() { + @Override + public void onUserSwitchComplete(int newUserId) { + getSemaphoreSwitchComplete(newUserId).release(); + } + + @Override + public void onLockedBootComplete(int newUserId) { + getSemaphoreBootComplete(newUserId).release(); + } + }; + + private final Map<Integer, Semaphore> mSemaphoresMapSwitchComplete = new ConcurrentHashMap<>(); + private Semaphore getSemaphoreSwitchComplete(final int userId) { + return mSemaphoresMapSwitchComplete.computeIfAbsent(userId, + (Integer absentKey) -> new Semaphore(0)); + } + + private final Map<Integer, Semaphore> mSemaphoresMapBootComplete = new ConcurrentHashMap<>(); + private Semaphore getSemaphoreBootComplete(final int userId) { + return mSemaphoresMapBootComplete.computeIfAbsent(userId, + (Integer absentKey) -> new Semaphore(0)); + } - public UserSwitchWaiter(String tag, int timeoutInSecond) { + public UserSwitchWaiter(String tag, int timeoutInSecond) throws RemoteException { mTag = tag; mTimeoutInSecond = timeoutInSecond; + mActivityManager = ActivityManager.getService(); + + mActivityManager.registerUserSwitchObserver(mUserSwitchObserver, mTag); + } + + @Override + public void close() throws IOException { + try { + mActivityManager.unregisterUserSwitchObserver(mUserSwitchObserver); + } catch (RemoteException e) { + Log.e(mTag, "Failed to unregister user switch observer", e); + } } public void runThenWaitUntilSwitchCompleted(int userId, FunctionalUtils.ThrowingRunnable runnable, Runnable onFail) throws RemoteException { - final CountDownLatch latch = new CountDownLatch(1); - ActivityManager.getService().registerUserSwitchObserver( - new UserSwitchObserver() { - @Override - public void onUserSwitchComplete(int newUserId) throws RemoteException { - if (userId == newUserId) { - latch.countDown(); - } - } - }, mTag); + final Semaphore semaphore = getSemaphoreSwitchComplete(userId); + semaphore.drainPermits(); runnable.run(); - waitForLatch(latch, onFail); + waitForSemaphore(semaphore, onFail); } public void runThenWaitUntilBootCompleted(int userId, FunctionalUtils.ThrowingRunnable runnable, Runnable onFail) throws RemoteException { - final CountDownLatch latch = new CountDownLatch(1); - ActivityManager.getService().registerUserSwitchObserver( - new UserSwitchObserver() { - @Override - public void onLockedBootComplete(int newUserId) { - if (userId == newUserId) { - latch.countDown(); - } - } - }, mTag); + final Semaphore semaphore = getSemaphoreBootComplete(userId); + semaphore.drainPermits(); runnable.run(); - waitForLatch(latch, onFail); + waitForSemaphore(semaphore, onFail); } - private void waitForLatch(CountDownLatch latch, Runnable onFail) { + private void waitForSemaphore(Semaphore semaphore, Runnable onFail) { boolean success = false; try { - success = latch.await(mTimeoutInSecond, TimeUnit.SECONDS); + success = semaphore.tryAcquire(mTimeoutInSecond, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.e(mTag, "Thread interrupted unexpectedly.", e); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index 59cd82e971bf..3bbc5a369684 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -176,12 +176,13 @@ public final class BatteryController extends RestrictingController { Slog.d(TAG, "maybeReportNewChargingStateLocked: " + powerConnected + "/" + stablePower + "/" + batteryNotLow); } + final long nowElapsed = sElapsedRealtimeClock.millis(); + mFlexibilityController.setConstraintSatisfied( - JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging()); - mFlexibilityController - .setConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow); + JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging(), nowElapsed); + mFlexibilityController.setConstraintSatisfied( + JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow, nowElapsed); - final long nowElapsed = sElapsedRealtimeClock.millis(); for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { final JobStatus ts = mTrackedTasks.valueAt(i); if (ts.hasChargingConstraint()) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 2e41dfd2888c..3ca1ad58955d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -137,9 +137,9 @@ public final class FlexibilityController extends StateController { new PrefetchController.PrefetchChangedListener() { @Override public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, - String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) { + String pkgName, long prevEstimatedLaunchTime, + long newEstimatedLaunchTime, long nowElapsed) { synchronized (mLock) { - final long nowElapsed = sElapsedRealtimeClock.millis(); final long prefetchThreshold = mPrefetchController.getLaunchTimeThresholdMs(); boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime @@ -158,8 +158,8 @@ public final class FlexibilityController extends StateController { if (!js.hasFlexibilityConstraint()) { continue; } - mFlexibilityTracker.resetJobNumDroppedConstraints(js); - mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); + mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); } } } @@ -191,7 +191,7 @@ public final class FlexibilityController extends StateController { js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); final long nowElapsed = sElapsedRealtimeClock.millis(); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); - mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); + mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); } } @@ -239,7 +239,7 @@ public final class FlexibilityController extends StateController { * Changes flexibility constraint satisfaction for affected jobs. */ @VisibleForTesting - void setConstraintSatisfied(int constraint, boolean state) { + void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) { synchronized (mLock) { final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; if (old == state) { @@ -255,8 +255,6 @@ public final class FlexibilityController extends StateController { // The rest did not have a change in state and are still satisfied or unsatisfied. final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied); - final long nowElapsed = sElapsedRealtimeClock.millis(); - // In order to get the range of all potentially satisfied jobs, we start at the number // of satisfied system-wide constraints and iterate to the max number of potentially // satisfied constraints, determined by how many job-specific constraints exist. @@ -329,10 +327,9 @@ public final class FlexibilityController extends StateController { @VisibleForTesting @GuardedBy("mLock") - int getCurPercentOfLifecycleLocked(JobStatus js) { + int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) { final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); - final long nowElapsed = sElapsedRealtimeClock.millis(); if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) { return 0; } @@ -414,8 +411,8 @@ public final class FlexibilityController extends StateController { .getJobsByNumRequiredConstraints(j); for (int i = 0; i < jobs.size(); i++) { JobStatus js = jobs.valueAt(i); - mFlexibilityTracker.resetJobNumDroppedConstraints(js); - mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); + mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); if (js.setFlexibilityConstraintSatisfied( nowElapsed, isFlexibilitySatisfiedLocked(js))) { changedJobs.add(js); @@ -479,8 +476,8 @@ public final class FlexibilityController extends StateController { mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js); } - public void resetJobNumDroppedConstraints(JobStatus js) { - final int curPercent = getCurPercentOfLifecycleLocked(js); + public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) { + final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); int toDrop = 0; final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (js.getPreferUnmetered() ? 1 : 0); @@ -489,7 +486,8 @@ public final class FlexibilityController extends StateController { toDrop++; } } - adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop); + adjustJobsRequiredConstraints( + js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed); } /** Returns all tracked jobs. */ @@ -502,13 +500,12 @@ public final class FlexibilityController extends StateController { * Returns false if the job status's number of flexible constraints is now 0. * Jobs with 0 required flexible constraints are removed from the tracker. */ - public boolean adjustJobsRequiredConstraints(JobStatus js, int n) { + public boolean adjustJobsRequiredConstraints(JobStatus js, int n, long nowElapsed) { if (n == 0) { return false; } remove(js); js.adjustNumRequiredFlexibleConstraints(n); - final long nowElapsed = sElapsedRealtimeClock.millis(); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); if (js.getNumRequiredFlexibleConstraints() <= 0) { maybeStopTrackingJobLocked(js, null, false); @@ -553,7 +550,7 @@ public final class FlexibilityController extends StateController { return js.getSourceUserId() == userId; } - public void scheduleDropNumConstraintsAlarm(JobStatus js) { + public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) { long nextTimeElapsed; synchronized (mLock) { final long earliest = getLifeCycleBeginningElapsedLocked(js); @@ -567,7 +564,7 @@ public final class FlexibilityController extends StateController { if (latest - nextTimeElapsed < mDeadlineProximityLimitMs) { mFlexibilityTracker.adjustJobsRequiredConstraints( - js, -js.getNumRequiredFlexibleConstraints()); + js, -js.getNumRequiredFlexibleConstraints(), nowElapsed); return; } addAlarm(js, nextTimeElapsed); @@ -578,21 +575,21 @@ public final class FlexibilityController extends StateController { protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { synchronized (mLock) { ArraySet<JobStatus> changedJobs = new ArraySet<>(); + final long nowElapsed = sElapsedRealtimeClock.millis(); for (int i = 0; i < expired.size(); i++) { JobStatus js = expired.valueAt(i); boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE); final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); - final long nowElapsed = sElapsedRealtimeClock.millis(); if (latest - nowElapsed < mDeadlineProximityLimitMs) { mFlexibilityTracker.adjustJobsRequiredConstraints(js, - -js.getNumRequiredFlexibleConstraints()); + -js.getNumRequiredFlexibleConstraints(), nowElapsed); } else { long nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js, earliest, latest); - if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1) + if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed) && nextTimeElapsed != NO_LIFECYCLE_END) { mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java index d5750f8d7974..dd0621728724 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java @@ -95,9 +95,10 @@ public final class IdleController extends RestrictingController implements Idlen */ @Override public void reportNewIdleState(boolean isIdle) { - mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_IDLE, isIdle); synchronized (mLock) { final long nowElapsed = sElapsedRealtimeClock.millis(); + mFlexibilityController.setConstraintSatisfied( + JobStatus.CONSTRAINT_IDLE, isIdle, nowElapsed); for (int i = mTrackedTasks.size()-1; i >= 0; i--) { mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(nowElapsed, isIdle); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 0945b7e796fd..e04cec30d26b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -105,7 +105,7 @@ public class PrefetchController extends StateController { public interface PrefetchChangedListener { /** Callback to inform listeners when estimated launch times change. */ void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName, - long prevEstimatedLaunchTime, long newEstimatedLaunchTime); + long prevEstimatedLaunchTime, long newEstimatedLaunchTime, long nowElapsed); } @SuppressWarnings("FieldCanBeLocal") @@ -308,8 +308,9 @@ public class PrefetchController extends StateController { final long nowElapsed = sElapsedRealtimeClock.millis(); updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed); for (int i = 0; i < mPrefetchChangedListeners.size(); i++) { - mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs, - userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime); + mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated( + jobs, userId, pkgName, prevEstimatedLaunchTime, + newEstimatedLaunchTime, nowElapsed); } if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) { mStateChangedListener.onControllerStateChanged(jobs); diff --git a/api/OWNERS b/api/OWNERS index 4d8ed0347f43..bf6216c168e8 100644 --- a/api/OWNERS +++ b/api/OWNERS @@ -3,7 +3,7 @@ hansson@google.com # Modularization team file:platform/packages/modules/common:/OWNERS -per-file Android.bp = file:platform/build/soong:/OWNERS +per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} # For metalava team to disable lint checks in platform -per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com
\ No newline at end of file +per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index dae4570456cd..814800bcd9e5 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -615,10 +615,6 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) { mWidth = limitedSize.width; mHeight = limitedSize.height; - SurfaceComposerClient::Transaction t; - t.setSize(mFlingerSurfaceControl, mWidth, mHeight); - t.apply(); - EGLConfig config = getEglConfig(mDisplay); EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr); if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) { diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist index 02f2df6167a5..502d8c6dadb1 100644 --- a/config/preloaded-classes-denylist +++ b/config/preloaded-classes-denylist @@ -9,3 +9,4 @@ android.net.rtp.AudioGroup android.net.rtp.AudioStream android.net.rtp.RtpStream java.util.concurrent.ThreadLocalRandom +com.android.internal.jank.InteractionJankMonitor$InstanceHolder diff --git a/core/api/current.txt b/core/api/current.txt index 3efe2ef5baea..795c43056d04 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -51979,6 +51979,7 @@ package android.view.accessibility { method public boolean isTextEntryKey(); method public boolean isTextSelectable(); method public boolean isVisibleToUser(); + method public void makeQueryableFromAppProcess(@NonNull android.view.View); method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View); method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int); method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(); @@ -52985,6 +52986,22 @@ package android.view.inputmethod { method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int); } + public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.graphics.RectF getDeletionArea(); + method public int getGranularity(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.DeleteGesture> CREATOR; + } + + public static final class DeleteGesture.Builder { + ctor public DeleteGesture.Builder(); + method @NonNull public android.view.inputmethod.DeleteGesture build(); + method @NonNull public android.view.inputmethod.DeleteGesture.Builder setDeletionArea(@NonNull android.graphics.RectF); + method @NonNull public android.view.inputmethod.DeleteGesture.Builder setFallbackText(@Nullable String); + method @NonNull public android.view.inputmethod.DeleteGesture.Builder setGranularity(int); + } + public final class EditorBoundsInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.graphics.RectF getEditorBounds(); @@ -53079,6 +53096,12 @@ package android.view.inputmethod { field public int token; } + public abstract class HandwritingGesture { + method @Nullable public String getFallbackText(); + field public static final int GRANULARITY_CHARACTER = 2; // 0x2 + field public static final int GRANULARITY_WORD = 1; // 0x1 + } + public final class InlineSuggestion implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.view.inputmethod.InlineSuggestionInfo getInfo(); @@ -53169,6 +53192,7 @@ package android.view.inputmethod { method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); + method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer); method public boolean performPrivateCommand(String, android.os.Bundle); method public default boolean performSpellCheck(); method public boolean reportFullscreenMode(boolean); @@ -53390,6 +53414,38 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int); } + public final class InsertGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.graphics.PointF getInsertionPoint(); + method @Nullable public String getTextToInsert(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertGesture> CREATOR; + } + + public static final class InsertGesture.Builder { + ctor public InsertGesture.Builder(); + method @NonNull public android.view.inputmethod.InsertGesture build(); + method @NonNull public android.view.inputmethod.InsertGesture.Builder setFallbackText(@Nullable String); + method @NonNull public android.view.inputmethod.InsertGesture.Builder setInsertionPoint(@NonNull android.graphics.PointF); + method @NonNull public android.view.inputmethod.InsertGesture.Builder setTextToInsert(@NonNull String); + } + + public final class SelectGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { + method public int describeContents(); + method public int getGranularity(); + method @NonNull public android.graphics.RectF getSelectionArea(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SelectGesture> CREATOR; + } + + public static final class SelectGesture.Builder { + ctor public SelectGesture.Builder(); + method @NonNull public android.view.inputmethod.SelectGesture build(); + method @NonNull public android.view.inputmethod.SelectGesture.Builder setFallbackText(@Nullable String); + method @NonNull public android.view.inputmethod.SelectGesture.Builder setGranularity(int); + method @NonNull public android.view.inputmethod.SelectGesture.Builder setSelectionArea(@NonNull android.graphics.RectF); + } + public final class SurroundingText implements android.os.Parcelable { ctor public SurroundingText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int); method public int describeContents(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index be84032df9a4..a0cc4f0d6775 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11479,6 +11479,7 @@ package android.service.notification { method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); + field public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; field public static final String FEEDBACK_RATING = "feedback.rating"; field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; field public static final int SOURCE_FROM_APP = 0; // 0x0 diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 25ef6e833565..c2b315f7088d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -980,7 +980,8 @@ public class Activity extends ContextThemeWrapper boolean mEnterAnimationComplete; private boolean mIsInMultiWindowMode; - private boolean mIsInPictureInPictureMode; + /** @hide */ + boolean mIsInPictureInPictureMode; private boolean mShouldDockBigOverlays; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b383d7daafd0..db7681640c67 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4177,7 +4177,8 @@ public final class ActivityThread extends ClientTransactionHandler private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.activity.isFinishing(), - /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false)); + /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false, + /* autoEnteringPip */ false)); executeTransaction(transaction); } @@ -4965,12 +4966,18 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving, - int configChanges, PendingTransactionActions pendingActions, String reason) { + int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions, + String reason) { if (userLeaving) { performUserLeavingActivity(r); } r.activity.mConfigChangeFlags |= configChanges; + if (autoEnteringPip) { + // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also + // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}. + r.activity.mIsInPictureInPictureMode = true; + } performPauseActivity(r, finished, reason, pendingActions); // Make sure any pending writes are now committed. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2a5916dfb6a8..d6b90a2792a9 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1906,6 +1906,7 @@ public class AppOpsManager { OP_SCHEDULE_EXACT_ALARM, OP_MANAGE_MEDIA, OP_TURN_SCREEN_ON, + OP_GET_USAGE_STATS, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 389da2d094d5..f322ca9654ed 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -96,8 +96,8 @@ public abstract class ClientTransactionHandler { /** Pause the activity. */ public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished, - boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, - String reason); + boolean userLeaving, int configChanges, boolean autoEnteringPip, + PendingTransactionActions pendingActions, String reason); /** * Resume the activity. diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 807bd5764cba..2b245aae915f 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -20,7 +20,6 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; -import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -68,7 +67,7 @@ public final class NotificationChannelGroup implements Parcelable { private CharSequence mName; private String mDescription; private boolean mBlocked; - private ParceledListSlice<NotificationChannel> mChannels; + private List<NotificationChannel> mChannels = new ArrayList<>(); // Bitwise representation of fields that have been changed by the user private int mUserLockedFields; @@ -103,8 +102,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { mDescription = null; } - mChannels = in.readParcelable( - NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class); + in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class); mBlocked = in.readBoolean(); mUserLockedFields = in.readInt(); } @@ -131,7 +129,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { dest.writeByte((byte) 0); } - dest.writeParcelable(mChannels, flags); + dest.writeParcelableList(mChannels, flags); dest.writeBoolean(mBlocked); dest.writeInt(mUserLockedFields); } @@ -161,7 +159,7 @@ public final class NotificationChannelGroup implements Parcelable { * Returns the list of channels that belong to this group */ public List<NotificationChannel> getChannels() { - return mChannels == null ? new ArrayList<>() : mChannels.getList(); + return mChannels; } /** @@ -195,8 +193,15 @@ public final class NotificationChannelGroup implements Parcelable { /** * @hide */ + public void addChannel(NotificationChannel channel) { + mChannels.add(channel); + } + + /** + * @hide + */ public void setChannels(List<NotificationChannel> channels) { - mChannels = new ParceledListSlice<>(channels); + mChannels = channels; } /** @@ -331,7 +336,7 @@ public final class NotificationChannelGroup implements Parcelable { proto.write(NotificationChannelGroupProto.NAME, mName.toString()); proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); - for (NotificationChannel channel : mChannels.getList()) { + for (NotificationChannel channel : mChannels) { channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS); } proto.end(token); diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 813e0f93a1f7..965e761ebfb3 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -39,13 +39,14 @@ public class PauseActivityItem extends ActivityLifecycleItem { private boolean mUserLeaving; private int mConfigChanges; private boolean mDontReport; + private boolean mAutoEnteringPip; @Override public void execute(ClientTransactionHandler client, ActivityClientRecord r, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions, - "PAUSE_ACTIVITY_ITEM"); + client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip, + pendingActions, "PAUSE_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -71,7 +72,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Obtain an instance initialized with provided params. */ public static PauseActivityItem obtain(boolean finished, boolean userLeaving, int configChanges, - boolean dontReport) { + boolean dontReport, boolean autoEnteringPip) { PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class); if (instance == null) { instance = new PauseActivityItem(); @@ -80,6 +81,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { instance.mUserLeaving = userLeaving; instance.mConfigChanges = configChanges; instance.mDontReport = dontReport; + instance.mAutoEnteringPip = autoEnteringPip; return instance; } @@ -94,6 +96,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { instance.mUserLeaving = false; instance.mConfigChanges = 0; instance.mDontReport = true; + instance.mAutoEnteringPip = false; return instance; } @@ -105,6 +108,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { mUserLeaving = false; mConfigChanges = 0; mDontReport = false; + mAutoEnteringPip = false; ObjectPool.recycle(this); } @@ -117,6 +121,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { dest.writeBoolean(mUserLeaving); dest.writeInt(mConfigChanges); dest.writeBoolean(mDontReport); + dest.writeBoolean(mAutoEnteringPip); } /** Read from Parcel. */ @@ -125,6 +130,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { mUserLeaving = in.readBoolean(); mConfigChanges = in.readInt(); mDontReport = in.readBoolean(); + mAutoEnteringPip = in.readBoolean(); } public static final @NonNull Creator<PauseActivityItem> CREATOR = @@ -148,7 +154,8 @@ public class PauseActivityItem extends ActivityLifecycleItem { } final PauseActivityItem other = (PauseActivityItem) o; return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving - && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport; + && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport + && mAutoEnteringPip == other.mAutoEnteringPip; } @Override @@ -158,12 +165,14 @@ public class PauseActivityItem extends ActivityLifecycleItem { result = 31 * result + (mUserLeaving ? 1 : 0); result = 31 * result + mConfigChanges; result = 31 * result + (mDontReport ? 1 : 0); + result = 31 * result + (mAutoEnteringPip ? 1 : 0); return result; } @Override public String toString() { return "PauseActivityItem{finished=" + mFinished + ",userLeaving=" + mUserLeaving - + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + "}"; + + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + + ",autoEnteringPip=" + mAutoEnteringPip + "}"; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 25ff8a78a0c8..de1d38a64163 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -227,7 +227,8 @@ public class TransactionExecutor { break; case ON_PAUSE: mTransactionHandler.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, mPendingActions, + false /* userLeaving */, 0 /* configChanges */, + false /* autoEnteringPip */, mPendingActions, "LIFECYCLER_PAUSE_ACTIVITY"); break; case ON_STOP: diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 7092e43596ec..7247ef77afb4 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -29,6 +29,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.CryptoObject; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; @@ -674,6 +675,45 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** + * Forwards BiometricStateListener to FaceService. + * + * @param listener new BiometricStateListener being added + * @hide + */ + public void registerBiometricStateListener(@NonNull BiometricStateListener listener) { + try { + mService.registerBiometricStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds a callback that gets called when the service registers all of the face + * authenticators (HALs). + * + * If the face authenticators are already registered when the callback is added, the + * callback is invoked immediately. + * + * The callback is automatically removed after it's invoked. + * + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void addAuthenticatorsRegisteredCallback( + IFaceAuthenticatorsRegisteredCallback callback) { + if (mService != null) { + try { + mService.addAuthenticatorsRegisteredCallback(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "addAuthenticatorsRegisteredCallback(): Service not connected!"); + } + } + + /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) diff --git a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl new file mode 100644 index 000000000000..78f978d21ed7 --- /dev/null +++ b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.face; + +import android.hardware.face.FaceSensorPropertiesInternal; +import java.util.List; + +/** + * Callback to notify FaceManager that FaceService has registered all of the + * face authenticators (HALs). + * See {@link android.hardware.face.IFaceService#registerAuthenticators}. + * + * @hide + */ +oneway interface IFaceAuthenticatorsRegisteredCallback { + /** + * Notifies FaceManager that all of the face authenticators have been registered. + * + * @param sensors A consolidated list of sensor properties for all of the authenticators. + */ + void onAllAuthenticatorsRegistered(in List<FaceSensorPropertiesInternal> sensors); +} diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 369248edd580..9b56f43a0f22 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -17,9 +17,11 @@ package android.hardware.face; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricStateListener; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; import android.hardware.face.FaceSensorPropertiesInternal; @@ -163,4 +165,11 @@ interface IFaceService { // hidlSensors must be non-null and empty. See AuthService.java @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors); + + // Adds a callback which gets called when the service registers all of the face + // authenticators. The callback is automatically removed after it's invoked. + void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback); + + // Registers BiometricStateListener. + void registerBiometricStateListener(IBiometricStateListener listener); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index cc7ed183ed64..1ba9a0471c88 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -202,8 +202,10 @@ interface IFingerprintService { void setSidefpsController(in ISidefpsController controller); // Registers BiometricStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerBiometricStateListener(IBiometricStateListener listener); // Sends a power button pressed event to all listeners. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") oneway void onPowerPressed(); } diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java index e38e61122786..3260713a7d14 100644 --- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java @@ -17,6 +17,7 @@ package android.inputmethodservice; import android.annotation.AnyThread; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; @@ -24,9 +25,13 @@ import android.os.RemoteException; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.DeleteGesture; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputContentInfo; +import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.SelectGesture; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; @@ -35,6 +40,8 @@ import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputConnectionCommandHeader; import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; /** * A stateless wrapper of {@link com.android.internal.inputmethod.IRemoteInputConnection} to @@ -591,6 +598,27 @@ final class IRemoteInputConnectionInvoker { } } + @AnyThread + public void performHandwritingGesture( + @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, + @Nullable IntConsumer consumer) { + // TODO(b/210039666): implement resultReceiver + try { + if (gesture instanceof SelectGesture) { + mConnection.performHandwritingSelectGesture( + createHeader(), (SelectGesture) gesture, null); + } else if (gesture instanceof InsertGesture) { + mConnection.performHandwritingInsertGesture( + createHeader(), (InsertGesture) gesture, null); + } else if (gesture instanceof DeleteGesture) { + mConnection.performHandwritingDeleteGesture( + createHeader(), (DeleteGesture) gesture, null); + } + } catch (RemoteException e) { + // TODO(b/210039666): return result + } + } + /** * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int, * int, AndroidFuture)}. diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index 2711c4f0db1f..694293c62bd7 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -17,6 +17,7 @@ package android.inputmethodservice; import android.annotation.AnyThread; +import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +29,7 @@ import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.SurroundingText; @@ -41,6 +43,8 @@ import com.android.internal.inputmethod.InputConnectionProtoDumper; import java.lang.ref.WeakReference; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; /** * Takes care of remote method invocations of {@link InputConnection} in the IME side. @@ -411,6 +415,13 @@ final class RemoteInputConnection implements InputConnection { } @AnyThread + public void performHandwritingGesture( + @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, + @Nullable IntConsumer consumer) { + mInvoker.performHandwritingGesture(gesture, executor, consumer); + } + + @AnyThread public boolean requestCursorUpdates(int cursorUpdateMode) { if (mCancellationGroup.isCanceled()) { return false; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 26600e256ca1..9d05cec06d01 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2317,6 +2317,11 @@ public abstract class BatteryStats { public abstract void finishIteratingHistoryLocked(); /** + * Return the base time offset for the battery history. + */ + public abstract long getHistoryBaseTime(); + + /** * Returns the number of times the device has been started. */ public abstract int getStartCount(); @@ -7601,6 +7606,8 @@ public abstract class BatteryStats { CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion()); + long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); + if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) { if (startIteratingHistoryLocked()) { try { diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 62ee408cffbf..fa6b1189f1da 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -76,6 +76,7 @@ interface IUserManager { String getUserAccount(int userId); void setUserAccount(int userId, String accountName); long getUserCreationTime(int userId); + boolean isUserSwitcherEnabled(int mUserId); boolean isRestricted(int userId); boolean canHaveRestrictedProfile(int userId); int getUserSerialNumber(int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ef04f641b0ee..738f9c9c4f13 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1995,7 +1995,8 @@ public class UserManager { /** @hide */ public UserManager(Context context, IUserManager service) { mService = service; - mContext = context.getApplicationContext(); + Context appContext = context.getApplicationContext(); + mContext = (appContext == null ? context : appContext); mUserId = context.getUserId(); } @@ -5246,23 +5247,13 @@ public class UserManager { }) @UserHandleAware public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) { - if (!supportsMultipleUsers()) { - return false; - } - if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId)) { - return false; - } - // If Demo Mode is on, don't show user switcher - if (isDeviceInDemoMode(mContext)) { - return false; - } - // Check the Settings.Global.USER_SWITCHER_ENABLED that the user can toggle on/off. - final boolean userSwitcherSettingOn = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.USER_SWITCHER_ENABLED, - Resources.getSystem().getBoolean(R.bool.config_showUserSwitcherByDefault) ? 1 : 0) - != 0; - if (!userSwitcherSettingOn) { - return false; + + try { + if (!mService.isUserSwitcherEnabled(mUserId)) { + return false; + } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } // The feature is enabled. But is it worth showing? diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8a50e79e8598..14598d558caa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9187,14 +9187,12 @@ public final class Settings { public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component"; /** - * The complications that are enabled to be shown over the screensaver by the user. Holds - * a comma separated list of - * {@link com.android.settingslib.dream.DreamBackend.ComplicationType}. + * Whether complications are enabled to be shown over the screensaver by the user. * * @hide */ - public static final String SCREENSAVER_ENABLED_COMPLICATIONS = - "screensaver_enabled_complications"; + public static final String SCREENSAVER_COMPLICATIONS_ENABLED = + "screensaver_complications_enabled"; /** diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 91042bfa3402..a38ef9676072 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -91,6 +91,21 @@ public abstract class NotificationAssistantService extends NotificationListenerS = "android.service.notification.NotificationAssistantService"; /** + * Activity Action: Show notification assistant detail setting page in NAS app. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = + "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; + + + /** * Data type: int, the feedback rating score provided by user. The score can be any integer * value depends on the experimental and feedback UX design. */ diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java index 95bcda5f7c55..9292e9608261 100644 --- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java +++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java @@ -1317,7 +1317,6 @@ final class RemoteSelectionToolbar { contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); - contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index b73ff901052f..0338cebf75c6 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -97,10 +97,6 @@ public class FeatureFlagUtils { /** @hide */ public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping"; - /** Flag to enable/disable guest mode UX changes as mentioned in b/214031645 - * @hide - */ - public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes"; /** Support Clear Calling feature. * @hide @@ -150,7 +146,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true"); DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false"); - DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false"); DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false"); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 84f04c12cf51..e0f02d6e567a 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -130,7 +130,6 @@ public final class SurfaceControl implements Parcelable { float x, float y); private static native void nativeSetScale(long transactionObj, long nativeObject, float x, float y); - private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h); private static native void nativeSetTransparentRegionHint(long transactionObj, long nativeObject, Region region); private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha); @@ -274,6 +273,9 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSanitize(long transactionObject); private static native void nativeSetDestinationFrame(long transactionObj, long nativeObject, int l, int t, int r, int b); + private static native void nativeSetDefaultApplyToken(IBinder token); + private static native IBinder nativeGetDefaultApplyToken(); + /** * Transforms that can be applied to buffers as they are displayed to a window. @@ -2774,6 +2776,22 @@ public final class SurfaceControl implements Parcelable { } /** + * + * @hide + */ + public static void setDefaultApplyToken(IBinder token) { + nativeSetDefaultApplyToken(token); + } + + /** + * + * @hide + */ + public static IBinder getDefaultApplyToken() { + return nativeGetDefaultApplyToken(); + } + + /** * Apply the transaction, clearing it's state, and making it usable * as a new transaction. */ @@ -2954,7 +2972,6 @@ public final class SurfaceControl implements Parcelable { @IntRange(from = 0) int w, @IntRange(from = 0) int h) { checkPreconditions(sc); mResizedSurfaces.put(sc, new Point(w, h)); - nativeSetSize(mNativeObject, sc.mNativeObject, w, h); return this; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a66427843af0..b6c92e3fd264 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -914,7 +914,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall && mRequestedVisible; final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight; final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility; - getLocationInSurface(mLocation); + getLocationInWindow(mLocation); final boolean positionChanged = mWindowSpaceLeft != mLocation[0] || mWindowSpaceTop != mLocation[1]; final boolean layoutSizeChanged = getWidth() != mScreenRect.width() @@ -925,7 +925,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (creating || formatChanged || sizeChanged || visibleChanged || (mUseAlpha && alphaChanged) || windowVisibleChanged || positionChanged || layoutSizeChanged || hintChanged) { - getLocationInWindow(mLocation); if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Changes: creating=" + creating diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9091b7955616..59bc061f54aa 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -173,6 +173,7 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener; @@ -5318,6 +5319,7 @@ public final class ViewRootImpl implements ViewParent, } mAccessibilityInteractionConnectionManager.ensureNoConnection(); + mAccessibilityInteractionConnectionManager.ensureNoDirectConnection(); removeSendWindowContentChangedCallback(); destroyHardwareRenderer(); @@ -9570,6 +9572,14 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Return the connection ID for the {@link AccessibilityInteractionController} of this instance. + * @see AccessibilityNodeInfo#makeQueryableFromAppProcess(View) + */ + public int getDirectAccessibilityConnectionId() { + return mAccessibilityInteractionConnectionManager.ensureDirectConnection(); + } + @Override public boolean showContextMenuForChild(View originalView) { return false; @@ -10445,6 +10455,8 @@ public final class ViewRootImpl implements ViewParent, */ final class AccessibilityInteractionConnectionManager implements AccessibilityStateChangeListener { + private int mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID; + @Override public void onAccessibilityStateChanged(boolean enabled) { if (enabled) { @@ -10488,6 +10500,21 @@ public final class ViewRootImpl implements ViewParent, mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow); } } + + public int ensureDirectConnection() { + if (mDirectConnectionId == AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) { + mDirectConnectionId = AccessibilityInteractionClient.addDirectConnection( + new AccessibilityInteractionConnection(ViewRootImpl.this)); + } + return mDirectConnectionId; + } + + public void ensureNoDirectConnection() { + if (mDirectConnectionId != AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) { + AccessibilityInteractionClient.removeConnection(mDirectConnectionId); + mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID; + } + } } final class HighContrastTextManager implements HighTextContrastChangeListener { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 68532781f08b..227a8ef6fd60 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -114,6 +114,10 @@ public final class AccessibilityInteractionClient private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = new SparseArray<>(); + // Used to generate connection ids for direct app-process connections. Start sufficiently far + // enough from the connection ids generated by AccessibilityManagerService. + private static int sDirectConnectionIdCounter = 1 << 30; + /** List of timestamps which indicate the latest time an a11y service receives a scroll event from a window, mapping from windowId -> timestamp. */ private static final SparseLongArray sScrollingWindows = new SparseLongArray(); @@ -232,6 +236,12 @@ public final class AccessibilityInteractionClient return; } synchronized (sConnectionCache) { + IAccessibilityServiceConnection existingConnection = getConnection(connectionId); + if (existingConnection instanceof DirectAccessibilityConnection) { + throw new IllegalArgumentException( + "Cannot add service connection with id " + connectionId + + " which conflicts with existing direct connection."); + } sConnectionCache.put(connectionId, connection); if (!initializeCache) { return; @@ -242,6 +252,33 @@ public final class AccessibilityInteractionClient } /** + * Adds a new {@link DirectAccessibilityConnection} using the provided + * {@link IAccessibilityInteractionConnection} to create a direct connection between + * this client and the {@link android.view.ViewRootImpl} for queries inside the app process. + * + * <p> + * See {@link DirectAccessibilityConnection} for supported methods. + * </p> + * + * @param connection The ViewRootImpl's {@link IAccessibilityInteractionConnection}. + */ + public static int addDirectConnection(IAccessibilityInteractionConnection connection) { + synchronized (sConnectionCache) { + int connectionId = sDirectConnectionIdCounter++; + if (getConnection(connectionId) != null) { + throw new IllegalArgumentException( + "Cannot add direct connection with existing id " + connectionId); + } + DirectAccessibilityConnection directAccessibilityConnection = + new DirectAccessibilityConnection(connection); + sConnectionCache.put(connectionId, directAccessibilityConnection); + // Do not use AccessibilityCache for this connection, since there is no corresponding + // AccessibilityService to handle cache invalidation events. + return connectionId; + } + } + + /** * Gets a cached associated with the connection id if available. * */ diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 953f2615b539..5d527500ff7b 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -58,6 +58,7 @@ import android.view.SurfaceView; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.widget.TextView; import com.android.internal.R; @@ -82,7 +83,9 @@ import java.util.Objects; * </p> * <p> * Once an accessibility node info is delivered to an accessibility service it is - * made immutable and calling a state mutation method generates an error. + * made immutable and calling a state mutation method generates an error. See + * {@link #makeQueryableFromAppProcess(View)} if you would like to inspect the + * node tree from the app process for testing or debugging tools. * </p> * <p> * Please refer to {@link android.accessibilityservice.AccessibilityService} for @@ -1156,8 +1159,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @param index The child index. * @return The child node. * - * @throws IllegalStateException If called outside of an AccessibilityService. - * + * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before + * calling {@link #makeQueryableFromAppProcess(View)}. */ public AccessibilityNodeInfo getChild(int index) { return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID); @@ -1171,7 +1174,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @param prefetchingStrategy the prefetching strategy. * @return The child node. * - * @throws IllegalStateException If called outside of an AccessibilityService. + * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before + * calling {@link #makeQueryableFromAppProcess(View)}. * * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching. */ @@ -1893,6 +1897,9 @@ public class AccessibilityNodeInfo implements Parcelable { * Gets the parent. * * @return The parent. + * + * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before + * calling {@link #makeQueryableFromAppProcess(View)}. */ public AccessibilityNodeInfo getParent() { enforceSealed(); @@ -1920,7 +1927,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @param prefetchingStrategy the prefetching strategy. * @return The parent. * - * @throws IllegalStateException If called outside of an AccessibilityService. + * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before + * calling {@link #makeQueryableFromAppProcess(View)}. * * @see #FLAG_PREFETCH_ANCESTORS * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST @@ -3642,6 +3650,47 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Connects this node to the View's root so that operations on this node can query the entire + * {@link AccessibilityNodeInfo} tree and perform accessibility actions on nodes. + * + * <p> + * This is intended for short-lived inspections from testing or debugging tools in the app + * process. After calling this method, all nodes linked to this node (children, ancestors, etc.) + * are also queryable. Operations on this node tree will only succeed as long as the associated + * view hierarchy remains attached to a window. + * </p> + * + * <p> + * Calling this method more than once on the same node is a no-op; if you wish to inspect a + * different view hierarchy then create a new node from any view in that hierarchy and call this + * method on that node. + * </p> + * + * <p> + * Testing or debugging tools should create this {@link AccessibilityNodeInfo} node using + * {@link View#createAccessibilityNodeInfo()} or {@link AccessibilityNodeProvider} and call this + * method, then navigate and interact with the node tree by calling methods on the node. + * </p> + * + * @param view The view that generated this node, or any view in the same view-root hierarchy. + * @throws IllegalStateException If called from an {@link AccessibilityService}, or if provided + * a {@link View} that is not attached to a window. + */ + public void makeQueryableFromAppProcess(@NonNull View view) { + enforceNotSealed(); + if (mConnectionId != UNDEFINED_CONNECTION_ID) { + return; + } + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + if (viewRootImpl == null) { + throw new IllegalStateException( + "Cannot link a node to a view that is not attached to a window."); + } + setConnectionId(viewRootImpl.getDirectAccessibilityConnectionId()); + } + + /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. @@ -3665,15 +3714,21 @@ public class AccessibilityNodeInfo implements Parcelable { return mSealed; } + private static boolean usingDirectConnection(int connectionId) { + return AccessibilityInteractionClient.getConnection( + connectionId) instanceof DirectAccessibilityConnection; + } + /** - * Enforces that this instance is sealed. + * Enforces that this instance is sealed, unless using a {@link DirectAccessibilityConnection} + * which allows queries while the node is not sealed. * * @throws IllegalStateException If this instance is not sealed. * * @hide */ protected void enforceSealed() { - if (!isSealed()) { + if (!usingDirectConnection(mConnectionId) && !isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on a not sealed instance."); } @@ -4499,7 +4554,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static boolean canPerformRequestOverConnection(int connectionId, int windowId, long accessibilityNodeId) { - return ((windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) + final boolean hasWindowId = windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + return ((usingDirectConnection(connectionId) || hasWindowId) && (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID) && (connectionId != UNDEFINED_CONNECTION_ID)); } diff --git a/core/java/android/view/accessibility/DirectAccessibilityConnection.java b/core/java/android/view/accessibility/DirectAccessibilityConnection.java new file mode 100644 index 000000000000..71746ee5dbab --- /dev/null +++ b/core/java/android/view/accessibility/DirectAccessibilityConnection.java @@ -0,0 +1,136 @@ +/* + * 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.accessibility; + +import android.accessibilityservice.IAccessibilityServiceConnection; +import android.graphics.Matrix; +import android.graphics.Region; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.view.MagnificationSpec; + +/** + * Minimal {@link IAccessibilityServiceConnection} implementation that interacts + * with the {@link android.view.AccessibilityInteractionController} of a + * {@link android.view.ViewRootImpl}. + * + * <p> + * Uses {@link android.view.ViewRootImpl}'s {@link IAccessibilityServiceConnection} that wraps + * {@link android.view.AccessibilityInteractionController} within the app process, so that no + * interprocess communication is performed. + * </p> + * + * <p> + * Only the following methods are supported: + * <li>{@link #findAccessibilityNodeInfoByAccessibilityId}</li> + * <li>{@link #findAccessibilityNodeInfosByText}</li> + * <li>{@link #findAccessibilityNodeInfosByViewId}</li> + * <li>{@link #findFocus}</li> + * <li>{@link #focusSearch}</li> + * <li>{@link #performAccessibilityAction}</li> + * </p> + * + * <p> + * Other methods are no-ops and return default values. + * </p> + */ +class DirectAccessibilityConnection extends IAccessibilityServiceConnection.Default { + private final IAccessibilityInteractionConnection mAccessibilityInteractionConnection; + + // Fetch all views, but do not use prefetching/cache since this "connection" does not + // receive cache invalidation events (as it is not linked to an AccessibilityService). + private static final int FETCH_FLAGS = + AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS + | AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; + private static final MagnificationSpec MAGNIFICATION_SPEC = new MagnificationSpec(); + private static final int PID = Process.myPid(); + private static final Region INTERACTIVE_REGION = null; + private static final float[] TRANSFORM_MATRIX = new float[9]; + + static { + Matrix.IDENTITY_MATRIX.getValues(TRANSFORM_MATRIX); + } + + DirectAccessibilityConnection( + IAccessibilityInteractionConnection accessibilityInteractionConnection) { + mAccessibilityInteractionConnection = accessibilityInteractionConnection; + } + + @Override + public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, + Bundle arguments) throws RemoteException { + mAccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId( + accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, + threadId, MAGNIFICATION_SPEC, TRANSFORM_MATRIX, arguments); + return new String[0]; + } + + @Override + public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId) + throws RemoteException { + mAccessibilityInteractionConnection.findAccessibilityNodeInfosByText(accessibilityNodeId, + text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId, + MAGNIFICATION_SPEC, TRANSFORM_MATRIX); + return new String[0]; + } + + @Override + public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, + long accessibilityNodeId, String viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId) + throws RemoteException { + mAccessibilityInteractionConnection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, + viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId, + MAGNIFICATION_SPEC, TRANSFORM_MATRIX); + return new String[0]; + } + + @Override + public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, + int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId) + throws RemoteException { + mAccessibilityInteractionConnection.findFocus(accessibilityNodeId, focusType, + INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId, + MAGNIFICATION_SPEC, TRANSFORM_MATRIX); + return new String[0]; + } + + @Override + public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, + int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId) + throws RemoteException { + mAccessibilityInteractionConnection.focusSearch(accessibilityNodeId, direction, + INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId, + MAGNIFICATION_SPEC, TRANSFORM_MATRIX); + return new String[0]; + } + + @Override + public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, + int action, Bundle arguments, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId) + throws RemoteException { + mAccessibilityInteractionConnection.performAccessibilityAction(accessibilityNodeId, action, + arguments, interactionId, callback, FETCH_FLAGS, PID, threadId); + return true; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt b/core/java/android/view/inputmethod/DeleteGesture.aidl index 1b7332265b7a..e9f31dd470ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt +++ b/core/java/android/view/inputmethod/DeleteGesture.aidl @@ -14,16 +14,6 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.wifi.data.model +package android.view.inputmethod; -/** Provides information about the current wifi state. */ -data class WifiModel( - /** See [android.net.wifi.WifiInfo.ssid]. */ - val ssid: String? = null, - /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ - val isPasspointAccessPoint: Boolean = false, - /** See [android.net.wifi.WifiInfo.isOsuAp]. */ - val isOnlineSignUpForPasspointAccessPoint: Boolean = false, - /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ - val passpointProviderFriendlyName: String? = null, -) +parcelable DeleteGesture;
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/DeleteGesture.java b/core/java/android/view/inputmethod/DeleteGesture.java new file mode 100644 index 000000000000..257254e5737a --- /dev/null +++ b/core/java/android/view/inputmethod/DeleteGesture.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 android.view.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.TextView; + +import java.util.Objects; + +/** + * A sub-class of {@link HandwritingGesture} for deleting an area of text. + * This class holds the information required for deletion of text in + * toolkit widgets like {@link TextView}. + */ +public final class DeleteGesture extends HandwritingGesture implements Parcelable { + + private @Granularity int mGranularity; + private RectF mArea; + + private DeleteGesture(@Granularity int granularity, RectF area, String fallbackText) { + mArea = area; + mGranularity = granularity; + mFallbackText = fallbackText; + } + + private DeleteGesture(@NonNull final Parcel source) { + mFallbackText = source.readString8(); + mGranularity = source.readInt(); + mArea = source.readTypedObject(RectF.CREATOR); + } + + /** + * Returns Granular level on which text should be operated. + * @see HandwritingGesture#GRANULARITY_CHARACTER + * @see HandwritingGesture#GRANULARITY_WORD + */ + @Granularity + public int getGranularity() { + return mGranularity; + } + + /** + * Returns the deletion area {@link RectF} in screen coordinates. + * + * Getter for deletion area set with {@link DeleteGesture.Builder#setDeletionArea(RectF)}. + * {@code null} if area was not set. + */ + @NonNull + public RectF getDeletionArea() { + return mArea; + } + + /** + * Builder for {@link DeleteGesture}. This class is not designed to be thread-safe. + */ + public static final class Builder { + private int mGranularity; + private RectF mArea; + private String mFallbackText; + + /** + * Set text deletion granularity. Intersecting words/characters will be + * included in the operation. + * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or + * {@link HandwritingGesture#GRANULARITY_CHARACTER}. + * @return {@link Builder}. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setGranularity(@Granularity int granularity) { + mGranularity = granularity; + return this; + } + + /** + * Set rectangular single/multiline text deletion area intersecting with text. + * + * The resulting deletion would be performed for all text intersecting rectangle. The + * deletion includes the first word/character in the rectangle, and the last + * word/character in the rectangle, and includes everything in between even if it's not + * in the rectangle. + * + * Intersection is determined using + * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes + * all the words with their width/height center included in the deletion rectangle. + * @param area {@link RectF} (in screen coordinates) for which text will be deleted. + * @see HandwritingGesture#GRANULARITY_WORD + * @see HandwritingGesture#GRANULARITY_CHARACTER + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setDeletionArea(@NonNull RectF area) { + mArea = area; + return this; + } + + /** + * Set fallback text that will be committed at current cursor position if there is no + * applicable text beneath the area of gesture. + * @param fallbackText text to set + */ + @NonNull + public Builder setFallbackText(@Nullable String fallbackText) { + mFallbackText = fallbackText; + return this; + } + + /** + * @return {@link DeleteGesture} using parameters in this {@link DeleteGesture.Builder}. + * @throws IllegalArgumentException if one or more positional parameters are not specified. + */ + @NonNull + public DeleteGesture build() { + if (mArea == null || mArea.isEmpty()) { + throw new IllegalArgumentException("Deletion area must be set."); + } + if (mGranularity <= GRANULARITY_UNDEFINED) { + throw new IllegalArgumentException("Deletion granularity must be set."); + } + return new DeleteGesture(mGranularity, mArea, mFallbackText); + } + } + + /** + * Used to make this class parcelable. + */ + public static final @android.annotation.NonNull Creator<DeleteGesture> CREATOR = + new Creator<DeleteGesture>() { + @Override + public DeleteGesture createFromParcel(Parcel source) { + return new DeleteGesture(source); + } + + @Override + public DeleteGesture[] newArray(int size) { + return new DeleteGesture[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mArea, mGranularity, mFallbackText); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DeleteGesture)) return false; + + DeleteGesture that = (DeleteGesture) o; + + if (mGranularity != that.mGranularity) return false; + if (!Objects.equals(mFallbackText, that.mFallbackText)) return false; + return Objects.equals(mArea, that.mArea); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mFallbackText); + dest.writeInt(mGranularity); + dest.writeTypedObject(mArea, flags); + } +} diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java new file mode 100644 index 000000000000..15824aeb0aeb --- /dev/null +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -0,0 +1,94 @@ +/* + * 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.inputmethod; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.graphics.RectF; +import android.inputmethodservice.InputMethodService; +import android.view.MotionEvent; + +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; + +/** + * Base class for Stylus handwriting gesture. + * + * During a stylus handwriting session, user can perform a stylus gesture operation like + * {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an + * area of text. IME is responsible for listening to Stylus {@link MotionEvent} using + * {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a + * gesture operation. + * While creating Gesture operations {@link SelectGesture}, {@link DeleteGesture}, + * , {@code Granularity} helps pick the correct granular level of text like word level + * {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}. + * + * @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer) + * @see InputMethodService#onStartStylusHandwriting() + */ +public abstract class HandwritingGesture { + + HandwritingGesture() {} + + static final int GRANULARITY_UNDEFINED = 0; + + /** + * Operate text per word basis. e.g. if selection includes width-wise center of the word, + * whole word is selected. + * <p> Strategy of operating at a granular level is maintained in the UI toolkit. + * A character/word/line is included if its center is within the gesture rectangle. + * e.g. if a selection {@link RectF} with {@link #GRANULARITY_WORD} includes width-wise + * center of the word, it should be selected. + * Similarly, text in a line should be included in the operation if rectangle includes + * line height center.</p> + * Refer to https://www.unicode.org/reports/tr29/#Word_Boundaries for more detail on how word + * breaks are decided. + */ + public static final int GRANULARITY_WORD = 1; + + /** + * Operate on text per character basis. i.e. each character is selected based on its + * intersection with selection rectangle. + * <p> Strategy of operating at a granular level is maintained in the UI toolkit. + * A character/word/line is included if its center is within the gesture rectangle. + * e.g. if a selection {@link RectF} with {@link #GRANULARITY_CHARACTER} includes width-wise + * center of the character, it should be selected. + * Similarly, text in a line should be included in the operation if rectangle includes + * line height center.</p> + */ + public static final int GRANULARITY_CHARACTER = 2; + + /** + * Granular level on which text should be operated. + */ + @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD}) + @interface Granularity {} + + @Nullable + String mFallbackText; + + /** + * The fallback text that will be committed at current cursor position if there is no applicable + * text beneath the area of gesture. + * For example, select can fail if gesture is drawn over area that has no text beneath. + * example 2: join can fail if the gesture is drawn over text but there is no whitespace. + */ + @Nullable + public String getFallbackText() { + return mFallbackText; + } +} diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index dac1be6f5a13..7b0270a1859f 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -31,6 +32,8 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; /** * The InputConnection interface is the communication channel from an @@ -968,6 +971,17 @@ public interface InputConnection { boolean performPrivateCommand(String action, Bundle data); /** + * Perform a handwriting gesture on text. + * + * @param gesture the gesture to perform + * @param executor if the caller passes a non-null consumer TODO(b/210039666): complete doc + * @param consumer if the caller passes a non-null receiver, the editor must invoke this + */ + default void performHandwritingGesture( + @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, + @Nullable IntConsumer consumer) {} + + /** * The editor is requested to call * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 7a88a75f93ad..56beddf2ef38 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -25,6 +26,9 @@ import android.view.KeyEvent; import com.android.internal.util.Preconditions; +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; + /** * <p>Wrapper class for proxying calls to another InputConnection. Subclass and have fun! */ @@ -323,6 +327,17 @@ public class InputConnectionWrapper implements InputConnection { * @throws NullPointerException if the target is {@code null}. */ @Override + public void performHandwritingGesture( + @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, + @Nullable IntConsumer consumer) { + mTarget.performHandwritingGesture(gesture, executor, consumer); + } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return mTarget.requestCursorUpdates(cursorUpdateMode); } diff --git a/core/java/android/view/inputmethod/InsertGesture.aidl b/core/java/android/view/inputmethod/InsertGesture.aidl new file mode 100644 index 000000000000..9cdb14a8b3ab --- /dev/null +++ b/core/java/android/view/inputmethod/InsertGesture.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable InsertGesture;
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java new file mode 100644 index 000000000000..2cf015a33283 --- /dev/null +++ b/core/java/android/view/inputmethod/InsertGesture.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.graphics.PointF; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import java.util.Objects; + +/** + * A sub-class of {@link HandwritingGesture} for inserting text at the defined insertion point. + * This class holds the information required for insertion of text in + * toolkit widgets like {@link TextView}. + */ +public final class InsertGesture extends HandwritingGesture implements Parcelable { + + private String mTextToInsert; + private PointF mPoint; + + private InsertGesture(String text, PointF point, String fallbackText) { + mPoint = point; + mTextToInsert = text; + mFallbackText = fallbackText; + } + + private InsertGesture(final Parcel source) { + mFallbackText = source.readString8(); + mTextToInsert = source.readString8(); + mPoint = source.readTypedObject(PointF.CREATOR); + } + + /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/ + @Nullable + public String getTextToInsert() { + return mTextToInsert; + } + + /** + * Returns the insertion point {@link PointF} (in screen coordinates) where + * {@link #getTextToInsert()} will be inserted. + */ + @Nullable + public PointF getInsertionPoint() { + return mPoint; + } + + /** + * Builder for {@link InsertGesture}. This class is not designed to be thread-safe. + */ + public static final class Builder { + private String mText; + private PointF mPoint; + private String mFallbackText; + + /** set the text that will be inserted at {@link #setInsertionPoint(PointF)} **/ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setTextToInsert(@NonNull String text) { + mText = text; + return this; + } + + /** + * Sets the insertion point (in screen coordinates) where {@link #setTextToInsert(String)} + * should be inserted. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setInsertionPoint(@NonNull PointF point) { + mPoint = point; + return this; + } + + /** + * Set fallback text that will be committed at current cursor position if there is no + * applicable text beneath the area of gesture. + * @param fallbackText text to set + */ + @NonNull + public Builder setFallbackText(@Nullable String fallbackText) { + mFallbackText = fallbackText; + return this; + } + + /** + * @return {@link InsertGesture} using parameters in this {@link InsertGesture.Builder}. + * @throws IllegalArgumentException if one or more positional parameters are not specified. + */ + @NonNull + public InsertGesture build() { + if (mPoint == null) { + throw new IllegalArgumentException("Insertion point must be set."); + } + if (TextUtils.isEmpty(mText)) { + throw new IllegalArgumentException("Text to insert must be non-empty."); + } + return new InsertGesture(mText, mPoint, mFallbackText); + } + } + + /** + * Used to make this class parcelable. + */ + public static final @android.annotation.NonNull Creator<InsertGesture> CREATOR = + new Creator<InsertGesture>() { + @Override + public InsertGesture createFromParcel(Parcel source) { + return new InsertGesture(source); + } + + @Override + public InsertGesture[] newArray(int size) { + return new InsertGesture[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mPoint, mTextToInsert, mFallbackText); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InsertGesture)) return false; + + InsertGesture that = (InsertGesture) o; + + if (!Objects.equals(mFallbackText, that.mFallbackText)) return false; + if (!Objects.equals(mTextToInsert, that.mTextToInsert)) return false; + return Objects.equals(mPoint, that.mPoint); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mFallbackText); + dest.writeString8(mTextToInsert); + dest.writeTypedObject(mPoint, flags); + } +} diff --git a/core/java/android/view/inputmethod/SelectGesture.aidl b/core/java/android/view/inputmethod/SelectGesture.aidl new file mode 100644 index 000000000000..65da4f340e8c --- /dev/null +++ b/core/java/android/view/inputmethod/SelectGesture.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable SelectGesture;
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/SelectGesture.java b/core/java/android/view/inputmethod/SelectGesture.java new file mode 100644 index 000000000000..f3cd71e1eed9 --- /dev/null +++ b/core/java/android/view/inputmethod/SelectGesture.java @@ -0,0 +1,191 @@ +/* + * 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.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.TextView; + +import java.util.Objects; + +/** + * A sub-class of {@link HandwritingGesture} for selecting an area of text. + * This class holds the information required for selection of text in + * toolkit widgets like {@link TextView}. + */ +public final class SelectGesture extends HandwritingGesture implements Parcelable { + + private @Granularity int mGranularity; + private RectF mArea; + + private SelectGesture(int granularity, RectF area, String fallbackText) { + mArea = area; + mGranularity = granularity; + mFallbackText = fallbackText; + } + + private SelectGesture(@NonNull Parcel source) { + mFallbackText = source.readString8(); + mGranularity = source.readInt(); + mArea = source.readTypedObject(RectF.CREATOR); + } + + /** + * Returns Granular level on which text should be operated. + * @see #GRANULARITY_CHARACTER + * @see #GRANULARITY_WORD + */ + @Granularity + public int getGranularity() { + return mGranularity; + } + + /** + * Returns the Selection area {@link RectF} in screen coordinates. + * + * Getter for selection area set with {@link Builder#setSelectionArea(RectF)}. {@code null} + * if area was not set. + */ + @NonNull + public RectF getSelectionArea() { + return mArea; + } + + + /** + * Builder for {@link SelectGesture}. This class is not designed to be thread-safe. + */ + public static final class Builder { + private int mGranularity; + private RectF mArea; + private String mFallbackText; + + /** + * Define text selection granularity. Intersecting words/characters will be + * included in the operation. + * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or + * {@link HandwritingGesture#GRANULARITY_CHARACTER}. + * @return {@link Builder}. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setGranularity(@Granularity int granularity) { + mGranularity = granularity; + return this; + } + + /** + * Set rectangular single/multiline text selection area intersecting with text. + * + * The resulting selection would be performed for all text intersecting rectangle. The + * selection includes the first word/character in the rectangle, and the last + * word/character in the rectangle, and includes everything in between even if it's not + * in the rectangle. + * + * Intersection is determined using + * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes + * all the words with their width/height center included in the selection rectangle. + * @param area {@link RectF} (in screen coordinates) for which text will be selection. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setSelectionArea(@NonNull RectF area) { + mArea = area; + return this; + } + + /** + * Set fallback text that will be committed at current cursor position if there is no + * applicable text beneath the area of gesture. + * @param fallbackText text to set + */ + @NonNull + public Builder setFallbackText(@Nullable String fallbackText) { + mFallbackText = fallbackText; + return this; + } + + /** + * @return {@link SelectGesture} using parameters in this {@link InsertGesture.Builder}. + * @throws IllegalArgumentException if one or more positional parameters are not specified. + */ + @NonNull + public SelectGesture build() { + if (mArea == null || mArea.isEmpty()) { + throw new IllegalArgumentException("Selection area must be set."); + } + if (mGranularity <= GRANULARITY_UNDEFINED) { + throw new IllegalArgumentException("Selection granularity must be set."); + } + return new SelectGesture(mGranularity, mArea, mFallbackText); + } + } + + /** + * Used to make this class parcelable. + */ + public static final @android.annotation.NonNull Parcelable.Creator<SelectGesture> CREATOR = + new Parcelable.Creator<SelectGesture>() { + @Override + public SelectGesture createFromParcel(Parcel source) { + return new SelectGesture(source); + } + + @Override + public SelectGesture[] newArray(int size) { + return new SelectGesture[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mGranularity, mArea, mFallbackText); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SelectGesture)) return false; + + SelectGesture that = (SelectGesture) o; + + if (mGranularity != that.mGranularity) return false; + if (!Objects.equals(mFallbackText, that.mFallbackText)) return false; + return Objects.equals(mArea, that.mArea); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mFallbackText); + dest.writeInt(mGranularity); + dest.writeTypedObject(mArea, flags); + } +} diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index e243aae81da4..efe3fd468e37 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -31,6 +31,7 @@ import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.SoundEffectConstants; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ExpandableListConnector.PositionMetadata; import com.android.internal.R; @@ -1144,6 +1145,24 @@ public class ExpandableListView extends ListView { return new ExpandableListContextMenuInfo(view, packedPosition, id); } + /** @hide */ + @Override + public void onInitializeAccessibilityNodeInfoForItem( + View view, int position, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoForItem(view, position, info); + + final PositionMetadata metadata = mConnector.getUnflattenedPos(position); + if (metadata.position.type == ExpandableListPosition.GROUP) { + if (isGroupExpanded(metadata.position.groupPos)) { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); + } else { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); + } + } + + metadata.recycle(); + } + /** * Gets the ID of the group or child at the given <code>position</code>. * This is useful since there is no ListAdapter ID -> ExpandableListAdapter diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 8407d10bc3ea..884ca77ea377 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -16,8 +16,10 @@ package android.window; +import android.os.IBinder; import android.view.RemoteAnimationDefinition; import android.window.ITaskFragmentOrganizer; +import android.window.WindowContainerTransaction; /** @hide */ interface ITaskFragmentOrganizerController { @@ -46,8 +48,15 @@ interface ITaskFragmentOrganizerController { void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId); /** - * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and - * only occupies a portion of Task bounds. - */ + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + */ boolean isActivityEmbedded(in IBinder activityToken); + + /** + * Notifies the server that the organizer has finished handling the given transaction. The + * server should apply the given {@link WindowContainerTransaction} for the necessary changes. + */ + void onTransactionHandled(in ITaskFragmentOrganizer organizer, in IBinder transactionToken, + in WindowContainerTransaction wct); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index cd15df84debd..7359172289cd 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -26,7 +26,6 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.annotation.TestApi; import android.content.Intent; import android.content.res.Configuration; @@ -141,6 +140,28 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** + * Notifies the server that the organizer has finished handling the given transaction. The + * server should apply the given {@link WindowContainerTransaction} for the necessary changes. + * + * @param transactionToken {@link TaskFragmentTransaction#getTransactionToken()} from + * {@link #onTransactionReady(TaskFragmentTransaction)} + * @param wct {@link WindowContainerTransaction} that the server should apply for + * update of the transaction. + * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission + * requirement. + * @hide + */ + public void onTransactionHandled(@NonNull IBinder transactionToken, + @NonNull WindowContainerTransaction wct) { + wct.setTaskFragmentOrganizer(mInterface); + try { + getController().onTransactionHandled(mInterface, transactionToken, wct); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Called when a TaskFragment is created and organized by this organizer. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No @@ -227,12 +248,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { /** * Called when the transaction is ready so that the organizer can update the TaskFragments based * on the changes in transaction. - * Note: {@link WindowOrganizer#applyTransaction} permission requirement is conditional for - * {@link TaskFragmentOrganizer}. - * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission * @hide */ - @SuppressLint("AndroidFrameworkRequiresPermission") public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); @@ -274,8 +291,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { "Unknown TaskFragmentEvent=" + change.getType()); } } - // TODO(b/240519866): notify TaskFragmentOrganizerController that the transition is done. - applyTransaction(wct); + + // Notify the server, and the server should apply the WindowContainerTransaction. + onTransactionHandled(transaction.getTransactionToken(), wct); } @Override diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 07e8e8c473c6..84a5fea9f57f 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.res.Configuration; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; @@ -41,19 +42,31 @@ import java.util.List; */ public final class TaskFragmentTransaction implements Parcelable { + /** Unique token to represent this transaction. */ + private final IBinder mTransactionToken; + + /** Changes in this transaction. */ private final ArrayList<Change> mChanges = new ArrayList<>(); - public TaskFragmentTransaction() {} + public TaskFragmentTransaction() { + mTransactionToken = new Binder(); + } private TaskFragmentTransaction(Parcel in) { + mTransactionToken = in.readStrongBinder(); in.readTypedList(mChanges, Change.CREATOR); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mTransactionToken); dest.writeTypedList(mChanges); } + public IBinder getTransactionToken() { + return mTransactionToken; + } + /** Adds a {@link Change} to this transaction. */ public void addChange(@Nullable Change change) { if (change != null) { @@ -74,7 +87,9 @@ public final class TaskFragmentTransaction implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("TaskFragmentTransaction{changes=["); + sb.append("TaskFragmentTransaction{token="); + sb.append(mTransactionToken); + sb.append(" changes=["); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { sb.append(','); diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index fcdcb2dadb54..8f6bc438ed9f 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -217,7 +217,11 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { case TYPE_HEADER_ALL_OTHERS: TextView textView = (TextView) itemView; if (itemType == TYPE_HEADER_SUGGESTED) { - setTextTo(textView, R.string.language_picker_section_suggested); + if (mCountryMode) { + setTextTo(textView, R.string.language_picker_regions_section_suggested); + } else { + setTextTo(textView, R.string.language_picker_section_suggested); + } } else { if (mCountryMode) { setTextTo(textView, R.string.region_picker_section_all); diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl index a7dd6f13b02d..7a219c61bafb 100644 --- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl +++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl @@ -17,11 +17,15 @@ package com.android.internal.inputmethod; import android.os.Bundle; +import android.os.ResultReceiver; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.DeleteGesture; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputContentInfo; +import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.SelectGesture; import android.view.inputmethod.TextAttribute; import com.android.internal.infra.AndroidFuture; @@ -86,6 +90,15 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; void performPrivateCommand(in InputConnectionCommandHeader header, String action, in Bundle data); + void performHandwritingSelectGesture(in InputConnectionCommandHeader header, + in SelectGesture gesture, in ResultReceiver resultReceiver); + + void performHandwritingInsertGesture(in InputConnectionCommandHeader header, + in InsertGesture gesture, in ResultReceiver resultReceiver); + + void performHandwritingDeleteGesture(in InputConnectionCommandHeader header, + in DeleteGesture gesture, in ResultReceiver resultReceiver); + void setComposingRegion(in InputConnectionCommandHeader header, int start, int end); void setComposingRegionWithTextAttribute(in InputConnectionCommandHeader header, int start, diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index b63ce1b22cdb..c65a69f05797 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -31,6 +31,7 @@ import android.annotation.Nullable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.ResultReceiver; import android.os.Trace; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -39,11 +40,15 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.DeleteGesture; import android.view.inputmethod.DumpableInputConnection; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.SelectGesture; import android.view.inputmethod.TextAttribute; import android.view.inputmethod.TextSnapshot; @@ -970,6 +975,67 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub @Dispatching(cancellable = true) @Override + public void performHandwritingSelectGesture( + InputConnectionCommandHeader header, SelectGesture gesture, + ResultReceiver resultReceiver) { + performHandwritingGestureInternal(header, gesture, resultReceiver); + } + + @Dispatching(cancellable = true) + @Override + public void performHandwritingInsertGesture( + InputConnectionCommandHeader header, InsertGesture gesture, + ResultReceiver resultReceiver) { + performHandwritingGestureInternal(header, gesture, resultReceiver); + } + + @Dispatching(cancellable = true) + @Override + public void performHandwritingDeleteGesture( + InputConnectionCommandHeader header, DeleteGesture gesture, + ResultReceiver resultReceiver) { + performHandwritingGestureInternal(header, gesture, resultReceiver); + } + + private <T extends HandwritingGesture> void performHandwritingGestureInternal( + InputConnectionCommandHeader header, T gesture, ResultReceiver resultReceiver) { + dispatchWithTracing("performHandwritingGesture", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "performHandwritingGesture on inactive InputConnection"); + return; + } + // TODO(b/210039666): implement resultReceiver + ic.performHandwritingGesture(gesture, null, null); + }); + } + + /** + * Dispatches {@link InputConnection#requestCursorUpdates(int)}. + * + * <p>This method is intended to be called only from {@link InputMethodManager}.</p> + * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)} + * @param cursorUpdateFilter the filter for + * {@link InputConnection#requestCursorUpdates(int, int)} + * @param imeDisplayId displayId on which IME is displayed. + */ + @Dispatching(cancellable = true) + public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter, + int imeDisplayId) { + final int currentSessionId = mCurrentSessionId.get(); + dispatchWithTracing("requestCursorUpdatesFromImm", () -> { + if (currentSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId); + }); + } + + @Dispatching(cancellable = true) + @Override public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode, int imeDisplayId, AndroidFuture future /* T=Boolean */) { dispatchWithTracing("requestCursorUpdates", future, () -> { diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 72de78c148f8..fc4e041058b1 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -87,6 +87,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UiThread; import android.annotation.WorkerThread; +import android.app.ActivityThread; import android.content.Context; import android.os.Build; import android.os.Handler; @@ -292,7 +293,10 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL, }; - private static volatile InteractionJankMonitor sInstance; + private static class InstanceHolder { + public static final InteractionJankMonitor INSTANCE = + new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME)); + } private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = this::updateProperties; @@ -384,15 +388,7 @@ public class InteractionJankMonitor { * @return instance of InteractionJankMonitor */ public static InteractionJankMonitor getInstance() { - // Use DCL here since this method might be invoked very often. - if (sInstance == null) { - synchronized (InteractionJankMonitor.class) { - if (sInstance == null) { - sInstance = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME)); - } - } - } - return sInstance; + return InstanceHolder.INSTANCE; } /** @@ -402,6 +398,11 @@ public class InteractionJankMonitor { */ @VisibleForTesting public InteractionJankMonitor(@NonNull HandlerThread worker) { + // Check permission early. + DeviceConfig.enforceReadPermission( + ActivityThread.currentApplication().getApplicationContext(), + DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR); + mRunningTrackers = new SparseArray<>(); mTimeoutActions = new SparseArray<>(); mWorker = worker; diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 6909965edcd8..962870e733f7 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -17,35 +17,25 @@ package com.android.internal.os; import android.annotation.Nullable; -import android.os.BatteryManager; -import android.os.BatteryStats.HistoryItem; -import android.os.BatteryStats.HistoryStepDetails; -import android.os.BatteryStats.HistoryTag; +import android.os.BatteryStats; import android.os.Parcel; -import android.os.ParcelFormatException; -import android.os.Process; import android.os.StatFs; import android.os.SystemClock; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; -import android.util.SparseArray; -import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ParseUtils; import java.io.File; -import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; /** * BatteryStatsHistory encapsulates battery history files. @@ -66,62 +56,57 @@ import java.util.concurrent.locks.ReentrantLock; * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by * locks on BatteryStatsImpl object. */ +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class BatteryStatsHistory { private static final boolean DEBUG = false; private static final String TAG = "BatteryStatsHistory"; // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - private static final int VERSION = 208; + public static final int VERSION = 208; - private static final String HISTORY_DIR = "battery-history"; - private static final String FILE_SUFFIX = ".bin"; + public static final String HISTORY_DIR = "battery-history"; + public static final String FILE_SUFFIX = ".bin"; private static final int MIN_FREE_SPACE = 100 * 1024 * 1024; - // Part of initial delta int that specifies the time delta. - static final int DELTA_TIME_MASK = 0x7ffff; - static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long - static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int - static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update. + public static final int DELTA_TIME_MASK = 0x7ffff; + public static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long + public static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int + public static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update. // Flag in delta int: a new battery level int follows. - static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000; + public static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000; // Flag in delta int: a new full state and battery status int follows. - static final int DELTA_STATE_FLAG = 0x00100000; + public static final int DELTA_STATE_FLAG = 0x00100000; // Flag in delta int: a new full state2 int follows. - static final int DELTA_STATE2_FLAG = 0x00200000; + public static final int DELTA_STATE2_FLAG = 0x00200000; // Flag in delta int: contains a wakelock or wakeReason tag. - static final int DELTA_WAKELOCK_FLAG = 0x00400000; + public static final int DELTA_WAKELOCK_FLAG = 0x00400000; // Flag in delta int: contains an event description. - static final int DELTA_EVENT_FLAG = 0x00800000; + public static final int DELTA_EVENT_FLAG = 0x00800000; // Flag in delta int: contains the battery charge count in uAh. - static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000; + public static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000; // These upper bits are the frequently changing state bits. - static final int DELTA_STATE_MASK = 0xfe000000; + public static final int DELTA_STATE_MASK = 0xfe000000; // These are the pieces of battery state that are packed in to the upper bits of // the state int that have been packed in to the first delta int. They must fit // in STATE_BATTERY_MASK. - static final int STATE_BATTERY_MASK = 0xff000000; - static final int STATE_BATTERY_STATUS_MASK = 0x00000007; - static final int STATE_BATTERY_STATUS_SHIFT = 29; - static final int STATE_BATTERY_HEALTH_MASK = 0x00000007; - static final int STATE_BATTERY_HEALTH_SHIFT = 26; - static final int STATE_BATTERY_PLUG_MASK = 0x00000003; - static final int STATE_BATTERY_PLUG_SHIFT = 24; + public static final int STATE_BATTERY_MASK = 0xff000000; + public static final int STATE_BATTERY_STATUS_MASK = 0x00000007; + public static final int STATE_BATTERY_STATUS_SHIFT = 29; + public static final int STATE_BATTERY_HEALTH_MASK = 0x00000007; + public static final int STATE_BATTERY_HEALTH_SHIFT = 26; + public static final int STATE_BATTERY_PLUG_MASK = 0x00000003; + public static final int STATE_BATTERY_PLUG_SHIFT = 24; // We use the low bit of the battery state int to indicate that we have full details // from a battery level change. - static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001; + public static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001; // Flag in history tag index: indicates that this is the first occurrence of this tag, // therefore the tag value is written in the parcel - static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000; + public static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000; + @Nullable + private final Supplier<Integer> mMaxHistoryFiles; private final Parcel mHistoryBuffer; - private final File mSystemDir; - private final HistoryStepDetailsCalculator mStepDetailsCalculator; private final File mHistoryDir; - private final Clock mClock; - - private int mMaxHistoryFiles; - private int mMaxHistoryBufferSize; - /** * The active history file that the history buffer is backed up into. */ @@ -159,77 +144,19 @@ public class BatteryStatsHistory { */ private int mParcelIndex = 0; - private final ReentrantLock mWriteLock = new ReentrantLock(); - - private final HistoryItem mHistoryCur = new HistoryItem(); - - private boolean mHaveBatteryLevel; - private boolean mRecordingHistory; - - private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe; - private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024; - - private final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>(); - private SparseArray<HistoryTag> mHistoryTags; - private final HistoryItem mHistoryLastWritten = new HistoryItem(); - private final HistoryItem mHistoryLastLastWritten = new HistoryItem(); - private final HistoryItem mHistoryAddTmp = new HistoryItem(); - private int mNextHistoryTagIdx = 0; - private int mNumHistoryTagChars = 0; - private int mHistoryBufferLastPos = -1; - private int mActiveHistoryStates = 0xffffffff; - private int mActiveHistoryStates2 = 0xffffffff; - private long mLastHistoryElapsedRealtimeMs = 0; - private long mTrackRunningHistoryElapsedRealtimeMs = 0; - private long mTrackRunningHistoryUptimeMs = 0; - private long mHistoryBaseTimeMs; - - private byte mLastHistoryStepLevel = 0; - - private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator; - - /** - * A delegate responsible for computing additional details for a step in battery history. - */ - public interface HistoryStepDetailsCalculator { - /** - * Returns additional details for the current history step or null. - */ - @Nullable - HistoryStepDetails getHistoryStepDetails(); - - /** - * Resets the calculator to get ready for a new battery session - */ - void clear(); - } - /** * Constructor * - * @param systemDir typically /data/system - * @param maxHistoryFiles the largest number of history buffer files to keep - * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps + * @param historyBuffer The in-memory history buffer. + * @param systemDir typically /data/system + * @param maxHistoryFiles the largest number of history buffer files to keep */ - public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { - this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize, - stepDetailsCalculator, clock); - initHistoryBuffer(); - } - - @VisibleForTesting public BatteryStatsHistory(Parcel historyBuffer, File systemDir, - int maxHistoryFiles, int maxHistoryBufferSize, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { + Supplier<Integer> maxHistoryFiles) { mHistoryBuffer = historyBuffer; - mSystemDir = systemDir; + mHistoryDir = new File(systemDir, HISTORY_DIR); mMaxHistoryFiles = maxHistoryFiles; - mMaxHistoryBufferSize = maxHistoryBufferSize; - mStepDetailsCalculator = stepDetailsCalculator; - mClock = clock; - mHistoryDir = new File(systemDir, HISTORY_DIR); mHistoryDir.mkdirs(); if (!mHistoryDir.exists()) { Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath()); @@ -265,81 +192,19 @@ public class BatteryStatsHistory { } } - public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { - mStepDetailsCalculator = stepDetailsCalculator; - mClock = clock; - - mHistoryBuffer = Parcel.obtain(); - mSystemDir = null; - mHistoryDir = null; - initHistoryBuffer(); - } - /** * Used when BatteryStatsImpl object is created from deserialization of a parcel, - * such as a checkin file. + * such as Settings app or checkin file. + * @param historyBuffer the history buffer */ - private BatteryStatsHistory(Parcel historyBuffer, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { - mHistoryBuffer = historyBuffer; - mClock = clock; - mSystemDir = null; + public BatteryStatsHistory(Parcel historyBuffer) { mHistoryDir = null; - mStepDetailsCalculator = stepDetailsCalculator; - } - - private void initHistoryBuffer() { - mHistoryBaseTimeMs = 0; - mLastHistoryElapsedRealtimeMs = 0; - mTrackRunningHistoryElapsedRealtimeMs = 0; - mTrackRunningHistoryUptimeMs = 0; - - mHistoryBuffer.setDataSize(0); - mHistoryBuffer.setDataPosition(0); - mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2); - mHistoryLastLastWritten.clear(); - mHistoryLastWritten.clear(); - mHistoryTagPool.clear(); - mNextHistoryTagIdx = 0; - mNumHistoryTagChars = 0; - mHistoryBufferLastPos = -1; - mActiveHistoryStates = 0xffffffff; - mActiveHistoryStates2 = 0xffffffff; - if (mStepDetailsCalculator != null) { - mStepDetailsCalculator.clear(); - } - } - - /** - * Changes the maximum number of history files to be kept. - */ - public void setMaxHistoryFiles(int maxHistoryFiles) { - mMaxHistoryFiles = maxHistoryFiles; - } - - /** - * Changes the maximum size of the history buffer, in bytes. - */ - public void setMaxHistoryBufferSize(int maxHistoryBufferSize) { - mMaxHistoryBufferSize = maxHistoryBufferSize; - } - - /** - * Creates a read-only copy of the battery history. Does not copy the files stored - * in the system directory, so it is not safe while actively writing history. - */ - public BatteryStatsHistory copy() { - // Make a copy of battery history to avoid concurrent modification. - Parcel historyBuffer = Parcel.obtain(); - historyBuffer.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize()); - return new BatteryStatsHistory(historyBuffer, mSystemDir, 0, 0, null, null); + mHistoryBuffer = historyBuffer; + mMaxHistoryFiles = null; } - /** - * Returns true if this instance only supports reading history. - */ - public boolean isReadOnly() { - return mActiveFile == null; + public File getHistoryDirectory() { + return mHistoryDir; } /** @@ -356,13 +221,12 @@ public class BatteryStatsHistory { /** * Create history AtomicFile from file number. - * * @param num file number. * @return AtomicFile object. */ private AtomicFile getFile(int num) { return new AtomicFile( - new File(mHistoryDir, num + FILE_SUFFIX)); + new File(mHistoryDir, num + FILE_SUFFIX)); } /** @@ -370,7 +234,7 @@ public class BatteryStatsHistory { * create next history file. */ public void startNextFile() { - if (mMaxHistoryFiles == 0) { + if (mMaxHistoryFiles == null) { Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history"); return; } @@ -400,7 +264,7 @@ public class BatteryStatsHistory { // if there are more history files than allowed, delete oldest history files. // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService // config at run time. - while (mFileNumbers.size() > mMaxHistoryFiles) { + while (mFileNumbers.size() > mMaxHistoryFiles.get()) { int oldest = mFileNumbers.get(0); getFile(oldest).delete(); mFileNumbers.remove(0); @@ -408,43 +272,36 @@ public class BatteryStatsHistory { } /** - * Clear history buffer and delete all existing history files. Active history file start from - * number 0 again. + * Delete all existing history files. Active history file start from number 0 again. */ - public void reset() { - if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!"); + public void resetAllFiles() { for (Integer i : mFileNumbers) { getFile(i).delete(); } mFileNumbers.clear(); mFileNumbers.add(0); setActiveFile(0); - - initHistoryBuffer(); } /** * Start iterating history files and history buffer. - * * @return always return true. */ - public BatteryStatsHistoryIterator iterate() { + public boolean startIteratingHistory() { mRecordCount = 0; mCurrentFileIndex = 0; mCurrentParcel = null; mCurrentParcelEnd = 0; mParcelIndex = 0; - mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this); - return mBatteryStatsHistoryIterator; + return true; } /** * Finish iterating history files and history buffer. */ - void finishIteratingHistory() { + public void finishIteratingHistory() { // setDataPosition so mHistoryBuffer Parcel can be written. mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); - mBatteryStatsHistoryIterator = null; if (DEBUG) { Slog.d(TAG, "Battery history records iterated: " + mRecordCount); } @@ -454,12 +311,11 @@ public class BatteryStatsHistory { * When iterating history files and history buffer, always start from the lowest numbered * history file, when reached the mActiveFile (highest numbered history file), do not read from * mActiveFile, read from history buffer instead because the buffer has more updated data. - * * @param out a history item. * @return The parcel that has next record. null if finished all history files and history - * buffer + * buffer */ - public Parcel getNextParcel(HistoryItem out) { + public Parcel getNextParcel(BatteryStats.HistoryItem out) { if (mRecordCount == 0) { // reset out if it is the first record. out.clear(); @@ -467,7 +323,8 @@ public class BatteryStatsHistory { ++mRecordCount; // First iterate through all records in current parcel. - if (mCurrentParcel != null) { + if (mCurrentParcel != null) + { if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) { // There are more records in current parcel. return mCurrentParcel; @@ -532,8 +389,7 @@ public class BatteryStatsHistory { /** * Read history file into a parcel. - * - * @param out the Parcel read into. + * @param out the Parcel read into. * @param file the File to read from. * @return true if success, false otherwise. */ @@ -546,8 +402,8 @@ public class BatteryStatsHistory { Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath() + " duration ms:" + (SystemClock.uptimeMillis() - start)); } - } catch (Exception e) { - Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e); + } catch(Exception e) { + Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); return false; } out.unmarshall(raw, 0, raw.length); @@ -557,7 +413,6 @@ public class BatteryStatsHistory { /** * Skip the header part of history parcel. - * * @param p history parcel to skip head. * @return true if version match, false if not. */ @@ -573,68 +428,18 @@ public class BatteryStatsHistory { } /** - * Writes the battery history contents for persistence. - */ - public void writeSummaryToParcel(Parcel out, boolean inclHistory) { - out.writeBoolean(inclHistory); - if (inclHistory) { - writeToParcel(out); - } - - out.writeInt(mHistoryTagPool.size()); - for (Map.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) { - HistoryTag tag = ent.getKey(); - out.writeInt(ent.getValue()); - out.writeString(tag.string); - out.writeInt(tag.uid); - } - } - - /** - * Reads battery history contents from a persisted parcel. - */ - public void readSummaryFromParcel(Parcel in) { - boolean inclHistory = in.readBoolean(); - if (inclHistory) { - readFromParcel(in); - } - - mHistoryTagPool.clear(); - mNextHistoryTagIdx = 0; - mNumHistoryTagChars = 0; - - int numTags = in.readInt(); - for (int i = 0; i < numTags; i++) { - int idx = in.readInt(); - String str = in.readString(); - int uid = in.readInt(); - HistoryTag tag = new HistoryTag(); - tag.string = str; - tag.uid = uid; - tag.poolIdx = idx; - mHistoryTagPool.put(tag, idx); - if (idx >= mNextHistoryTagIdx) { - mNextHistoryTagIdx = idx + 1; - } - mNumHistoryTagChars += tag.string.length() + 1; - } - } - - /** * Read all history files and serialize into a big Parcel. * Checkin file calls this method. * * @param out the output parcel */ public void writeToParcel(Parcel out) { - writeHistoryBuffer(out); writeToParcel(out, false /* useBlobs */); } /** * This is for Settings app, when Settings app receives big history parcel, it call * this method to parse it into list of parcels. - * * @param out the output parcel */ public void writeToBatteryUsageStatsParcel(Parcel out) { @@ -645,13 +450,13 @@ public class BatteryStatsHistory { private void writeToParcel(Parcel out, boolean useBlobs) { final long start = SystemClock.uptimeMillis(); out.writeInt(mFileNumbers.size() - 1); - for (int i = 0; i < mFileNumbers.size() - 1; i++) { + for(int i = 0; i < mFileNumbers.size() - 1; i++) { AtomicFile file = getFile(mFileNumbers.get(i)); byte[] raw = new byte[0]; try { raw = file.readFully(); - } catch (Exception e) { - Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e); + } catch(Exception e) { + Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); } if (useBlobs) { out.writeBlob(raw); @@ -675,55 +480,17 @@ public class BatteryStatsHistory { Parcel historyBuffer = Parcel.obtain(); historyBuffer.unmarshall(historyBlob, 0, historyBlob.length); - BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer, null, - Clock.SYSTEM_CLOCK); + BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer); history.readFromParcel(in, true /* useBlobs */); return history; } /** - * Read history from a check-in file. - */ - public boolean readSummary() { - if (mActiveFile == null) { - Slog.w(TAG, "readSummary: no history file associated with this instance"); - return false; - } - - Parcel parcel = Parcel.obtain(); - try { - final long start = SystemClock.uptimeMillis(); - if (mActiveFile.exists()) { - byte[] raw = mActiveFile.readFully(); - if (raw.length > 0) { - parcel.unmarshall(raw, 0, raw.length); - parcel.setDataPosition(0); - readHistoryBuffer(parcel); - } - if (DEBUG) { - Slog.d(TAG, "read history file::" - + mActiveFile.getBaseFile().getPath() - + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis() - - start)); - } - } - } catch (Exception e) { - Slog.e(TAG, "Error reading battery history", e); - reset(); - return false; - } finally { - parcel.recycle(); - } - return true; - } - - /** * This is for the check-in file, which has all history files embedded. * * @param in the input parcel. */ public void readFromParcel(Parcel in) { - readHistoryBuffer(in); readFromParcel(in, false /* useBlobs */); } @@ -731,7 +498,7 @@ public class BatteryStatsHistory { final long start = SystemClock.uptimeMillis(); mHistoryParcels = new ArrayList<>(); final int count = in.readInt(); - for (int i = 0; i < count; i++) { + for(int i = 0; i < count; i++) { byte[] temp = useBlobs ? in.readBlob() : in.createByteArray(); if (temp == null || temp.length == 0) { continue; @@ -754,12 +521,10 @@ public class BatteryStatsHistory { return stats.getAvailableBytes() > MIN_FREE_SPACE; } - @VisibleForTesting public List<Integer> getFilesNumbers() { return mFileNumbers; } - @VisibleForTesting public AtomicFile getActiveFile() { return mActiveFile; } @@ -769,972 +534,15 @@ public class BatteryStatsHistory { */ public int getHistoryUsedSize() { int ret = 0; - for (int i = 0; i < mFileNumbers.size() - 1; i++) { + for(int i = 0; i < mFileNumbers.size() - 1; i++) { ret += getFile(mFileNumbers.get(i)).getBaseFile().length(); } ret += mHistoryBuffer.dataSize(); if (mHistoryParcels != null) { - for (int i = 0; i < mHistoryParcels.size(); i++) { + for(int i = 0; i < mHistoryParcels.size(); i++) { ret += mHistoryParcels.get(i).dataSize(); } } return ret; } - - /** - * Enables/disables recording of history. When disabled, all "record*" calls are a no-op. - */ - public void setHistoryRecordingEnabled(boolean enabled) { - mRecordingHistory = enabled; - } - - /** - * Returns true if history recording is enabled. - */ - public boolean isRecordingHistory() { - return mRecordingHistory; - } - - /** - * Forces history recording regardless of charging state. - */ - @VisibleForTesting - public void forceRecordAllHistory() { - mHaveBatteryLevel = true; - mRecordingHistory = true; - } - - /** - * Starts a history buffer by recording the current wall-clock time. - */ - public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs, - boolean reset) { - mRecordingHistory = true; - mHistoryCur.currentTime = mClock.currentTimeMillis(); - writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, - reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME); - mHistoryCur.currentTime = 0; - } - - /** - * Prepares to continue recording after restoring previous history from persistent storage. - */ - public void continueRecordingHistory() { - if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) { - return; - } - - mRecordingHistory = true; - final long elapsedRealtimeMs = mClock.elapsedRealtime(); - final long uptimeMs = mClock.uptimeMillis(); - writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START); - startRecordingHistory(elapsedRealtimeMs, uptimeMs, false); - } - - /** - * Notes the current battery state to be reflected in the next written history item. - */ - public void setBatteryState(boolean charging, int status, int level, int chargeUah) { - mHaveBatteryLevel = true; - setChargingState(charging); - mHistoryCur.batteryStatus = (byte) status; - mHistoryCur.batteryLevel = (byte) level; - mHistoryCur.batteryChargeUah = chargeUah; - } - - /** - * Notes the current battery state to be reflected in the next written history item. - */ - public void setBatteryState(int status, int level, int health, int plugType, int temperature, - int voltageMv, int chargeUah) { - mHaveBatteryLevel = true; - mHistoryCur.batteryStatus = (byte) status; - mHistoryCur.batteryLevel = (byte) level; - mHistoryCur.batteryHealth = (byte) health; - mHistoryCur.batteryPlugType = (byte) plugType; - mHistoryCur.batteryTemperature = (short) temperature; - mHistoryCur.batteryVoltage = (char) voltageMv; - mHistoryCur.batteryChargeUah = chargeUah; - } - - /** - * Notes the current power plugged-in state to be reflected in the next written history item. - */ - public void setPluggedInState(boolean pluggedIn) { - if (pluggedIn) { - mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; - } else { - mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; - } - } - - /** - * Notes the current battery charging state to be reflected in the next written history item. - */ - public void setChargingState(boolean charging) { - if (charging) { - mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG; - } else { - mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG; - } - } - - /** - * Records a history event with the given code, name and UID. - */ - public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name, - int uid) { - mHistoryCur.eventCode = code; - mHistoryCur.eventTag = mHistoryCur.localEventTag; - mHistoryCur.eventTag.string = name; - mHistoryCur.eventTag.uid = uid; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a time change event. - */ - public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) { - if (!mRecordingHistory) { - return; - } - - mHistoryCur.currentTime = currentTimeMs; - writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, - HistoryItem.CMD_CURRENT_TIME); - mHistoryCur.currentTime = 0; - } - - /** - * Records a system shutdown event. - */ - public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) { - if (!mRecordingHistory) { - return; - } - - mHistoryCur.currentTime = currentTimeMs; - writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN); - mHistoryCur.currentTime = 0; - } - - /** - * Records a battery state change event. - */ - public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel, - boolean isPlugged) { - mHistoryCur.batteryLevel = (byte) batteryLevel; - setPluggedInState(isPlugged); - if (DEBUG) { - Slog.v(TAG, "Battery unplugged to: " - + Integer.toHexString(mHistoryCur.states)); - } - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a history item with the amount of charge consumed by WiFi. Used on certain devices - * equipped with on-device power metering. - */ - public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs, - double monitoredRailChargeMah) { - mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a wakelock start event. - */ - public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName, - int uid) { - mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; - mHistoryCur.wakelockTag.string = historyName; - mHistoryCur.wakelockTag.uid = uid; - recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG); - } - - /** - * Updates the previous history event with a wakelock name and UID. - */ - public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName, - int uid) { - if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) { - return false; - } - if (mHistoryLastWritten.wakelockTag != null) { - // We'll try to update the last tag. - mHistoryLastWritten.wakelockTag = null; - mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; - mHistoryCur.wakelockTag.string = historyName; - mHistoryCur.wakelockTag.uid = uid; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - return true; - } - - /** - * Records an event when some state flag changes to true. - */ - public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) { - mHistoryCur.states |= stateFlags; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records an event when some state flag changes to false. - */ - public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) { - mHistoryCur.states &= ~stateFlags; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records an event when some state flags change to true and some to false. - */ - public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags, - int stateStopFlags) { - mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records an event when some state2 flag changes to true. - */ - public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) { - mHistoryCur.states2 |= stateFlags; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records an event when some state2 flag changes to false. - */ - public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) { - mHistoryCur.states2 &= ~stateFlags; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records an wakeup event. - */ - public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) { - mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag; - mHistoryCur.wakeReasonTag.string = reason; - mHistoryCur.wakeReasonTag.uid = 0; - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a screen brightness change event. - */ - public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs, - int brightnessBin) { - mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK) - | (brightnessBin << HistoryItem.STATE_BRIGHTNESS_SHIFT); - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a GNSS signal level change event. - */ - public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs, - int signalLevel) { - mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK) - | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT); - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a device idle mode change event. - */ - public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) { - mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK) - | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT); - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a telephony state change event. - */ - public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag, - int removeStateFlag, int state, int signalStrength) { - mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag; - if (state != -1) { - mHistoryCur.states = - (mHistoryCur.states & ~HistoryItem.STATE_PHONE_STATE_MASK) - | (state << HistoryItem.STATE_PHONE_STATE_SHIFT); - } - if (signalStrength != -1) { - mHistoryCur.states = - (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK) - | (signalStrength << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT); - } - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a data connection type change event. - */ - public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs, - int dataConnectionType) { - mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_DATA_CONNECTION_MASK) - | (dataConnectionType << HistoryItem.STATE_DATA_CONNECTION_SHIFT); - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a WiFi supplicant state change event. - */ - public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, - int supplState) { - mHistoryCur.states2 = - (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK) - | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT); - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Records a WiFi signal strength change event. - */ - public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs, - int strengthBin) { - mHistoryCur.states2 = - (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK) - | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT); - writeHistoryItem(elapsedRealtimeMs, uptimeMs); - } - - /** - * Writes the current history item to history. - */ - public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) { - if (mTrackRunningHistoryElapsedRealtimeMs != 0) { - final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs; - final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs; - if (diffUptimeMs < (diffElapsedMs - 20)) { - final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs); - mHistoryAddTmp.setTo(mHistoryLastWritten); - mHistoryAddTmp.wakelockTag = null; - mHistoryAddTmp.wakeReasonTag = null; - mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE; - mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG; - writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp); - } - } - mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG; - mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs; - mTrackRunningHistoryUptimeMs = uptimeMs; - writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur); - } - - private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { - if (!mHaveBatteryLevel || !mRecordingHistory) { - return; - } - - final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time; - final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates); - final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2); - final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states; - final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2; - if (DEBUG) { - Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff=" - + Integer.toHexString(diffStates) + " lastDiff=" - + Integer.toHexString(lastDiffStates) + " diff2=" - + Integer.toHexString(diffStates2) + " lastDiff2=" - + Integer.toHexString(lastDiffStates2)); - } - if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE - && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0 - && (diffStates2 & lastDiffStates2) == 0 - && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence) - && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null) - && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null) - && mHistoryLastWritten.stepDetails == null - && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE - || cur.eventCode == HistoryItem.EVENT_NONE) - && mHistoryLastWritten.batteryLevel == cur.batteryLevel - && mHistoryLastWritten.batteryStatus == cur.batteryStatus - && mHistoryLastWritten.batteryHealth == cur.batteryHealth - && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType - && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature - && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) { - // We can merge this new change in with the last one. Merging is - // allowed as long as only the states have changed, and within those states - // as long as no bit has changed both between now and the last entry, as - // well as the last entry and the one before it (so we capture any toggles). - if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos); - mHistoryBuffer.setDataSize(mHistoryBufferLastPos); - mHistoryBuffer.setDataPosition(mHistoryBufferLastPos); - mHistoryBufferLastPos = -1; - elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs; - // If the last written history had a wakelock tag, we need to retain it. - // Note that the condition above made sure that we aren't in a case where - // both it and the current history item have a wakelock tag. - if (mHistoryLastWritten.wakelockTag != null) { - cur.wakelockTag = cur.localWakelockTag; - cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag); - } - // If the last written history had a wake reason tag, we need to retain it. - // Note that the condition above made sure that we aren't in a case where - // both it and the current history item have a wakelock tag. - if (mHistoryLastWritten.wakeReasonTag != null) { - cur.wakeReasonTag = cur.localWakeReasonTag; - cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag); - } - // If the last written history had an event, we need to retain it. - // Note that the condition above made sure that we aren't in a case where - // both it and the current history item have an event. - if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) { - cur.eventCode = mHistoryLastWritten.eventCode; - cur.eventTag = cur.localEventTag; - cur.eventTag.setTo(mHistoryLastWritten.eventTag); - } - mHistoryLastWritten.setTo(mHistoryLastLastWritten); - } - final int dataSize = mHistoryBuffer.dataSize(); - - if (dataSize >= mMaxHistoryBufferSize) { - if (mMaxHistoryBufferSize == 0) { - Slog.wtf(TAG, "mMaxHistoryBufferSize should not be zero when writing history"); - mMaxHistoryBufferSize = 1024; - } - - //open a new history file. - final long start = SystemClock.uptimeMillis(); - writeHistory(); - if (DEBUG) { - Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:" - + (SystemClock.uptimeMillis() - start)); - } - startNextFile(); - mHistoryBuffer.setDataSize(0); - mHistoryBuffer.setDataPosition(0); - mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2); - mHistoryBufferLastPos = -1; - mHistoryLastWritten.clear(); - mHistoryLastLastWritten.clear(); - - // Mark every entry in the pool with a flag indicating that the tag - // has not yet been encountered while writing the current history buffer. - for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) { - entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG); - } - // Make a copy of mHistoryCur. - HistoryItem copy = new HistoryItem(); - copy.setTo(cur); - // startRecordingHistory will reset mHistoryCur. - startRecordingHistory(elapsedRealtimeMs, uptimeMs, false); - // Add the copy into history buffer. - writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE); - return; - } - - if (dataSize == 0) { - // The history is currently empty; we need it to start with a time stamp. - cur.currentTime = mClock.currentTimeMillis(); - writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_RESET); - } - writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE); - } - - private void writeHistoryItem(long elapsedRealtimeMs, - @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) { - if (mBatteryStatsHistoryIterator != null) { - throw new IllegalStateException("Can't do this while iterating history!"); - } - mHistoryBufferLastPos = mHistoryBuffer.dataPosition(); - mHistoryLastLastWritten.setTo(mHistoryLastWritten); - final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence; - mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur); - mHistoryLastWritten.tagsFirstOccurrence = hasTags; - mHistoryLastWritten.states &= mActiveHistoryStates; - mHistoryLastWritten.states2 &= mActiveHistoryStates2; - writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); - mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; - cur.wakelockTag = null; - cur.wakeReasonTag = null; - cur.eventCode = HistoryItem.EVENT_NONE; - cur.eventTag = null; - cur.tagsFirstOccurrence = false; - if (DEBUG) { - Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos - + " now " + mHistoryBuffer.dataPosition() - + " size is now " + mHistoryBuffer.dataSize()); - } - } - - /* - The history delta format uses flags to denote further data in subsequent ints in the parcel. - - There is always the first token, which may contain the delta time, or an indicator of - the length of the time (int or long) following this token. - - First token: always present, - 31 23 15 7 0 - █M|L|K|J|I|H|G|F█E|D|C|B|A|T|T|T█T|T|T|T|T|T|T|T█T|T|T|T|T|T|T|T█ - - T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately - follows containing the time, and 0x7ffff indicates a long immediately follows with the - delta time. - A: battery level changed and an int follows with battery data. - B: state changed and an int follows with state change data. - C: state2 has changed and an int follows with state2 change data. - D: wakelock/wakereason has changed and an wakelock/wakereason struct follows. - E: event data has changed and an event struct follows. - F: battery charge in coulombs has changed and an int with the charge follows. - G: state flag denoting that the mobile radio was active. - H: state flag denoting that the wifi radio was active. - I: state flag denoting that a wifi scan occurred. - J: state flag denoting that a wifi full lock was held. - K: state flag denoting that the gps was on. - L: state flag denoting that a wakelock was held. - M: state flag denoting that the cpu was running. - - Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows - with the time delta. - - Battery level int: if A in the first token is set, - 31 23 15 7 0 - █L|L|L|L|L|L|L|T█T|T|T|T|T|T|T|T█T|V|V|V|V|V|V|V█V|V|V|V|V|V|V|D█ - - D: indicates that extra history details follow. - V: the battery voltage. - T: the battery temperature. - L: the battery level (out of 100). - - State change int: if B in the first token is set, - 31 23 15 7 0 - █S|S|S|H|H|H|P|P█F|E|D|C|B| | |A█ | | | | | | | █ | | | | | | | █ - - A: wifi multicast was on. - B: battery was plugged in. - C: screen was on. - D: phone was scanning for signal. - E: audio was on. - F: a sensor was active. - - State2 change int: if C in the first token is set, - 31 23 15 7 0 - █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█ - - A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}. - B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4. - C: a bluetooth scan was active. - D: the camera was active. - E: bluetooth was on. - F: a phone call was active. - G: the device was charging. - H: 2 bits indicating the device-idle (doze) state: off, light, full - I: the flashlight was on. - J: wifi was on. - K: wifi was running. - L: video was playing. - M: power save mode was on. - - Wakelock/wakereason struct: if D in the first token is set, - Event struct: if E in the first token is set, - History step details struct: if D in the battery level int is set, - - Battery charge int: if F in the first token is set, an int representing the battery charge - in coulombs follows. - */ - /** - * Writes the delta between the previous and current history items into history buffer. - */ - public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) { - if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) { - dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS); - cur.writeToParcel(dest, 0); - return; - } - - final long deltaTime = cur.time - last.time; - final int lastBatteryLevelInt = buildBatteryLevelInt(last); - final int lastStateInt = buildStateInt(last); - - int deltaTimeToken; - if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) { - deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG; - } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) { - deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT; - } else { - deltaTimeToken = (int) deltaTime; - } - int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK); - final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel - ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0; - mLastHistoryStepLevel = cur.batteryLevel; - final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails; - final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt; - if (batteryLevelIntChanged) { - firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG; - } - final int stateInt = buildStateInt(cur); - final boolean stateIntChanged = stateInt != lastStateInt; - if (stateIntChanged) { - firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG; - } - final boolean state2IntChanged = cur.states2 != last.states2; - if (state2IntChanged) { - firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG; - } - if (cur.wakelockTag != null || cur.wakeReasonTag != null) { - firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG; - } - if (cur.eventCode != HistoryItem.EVENT_NONE) { - firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG; - } - - final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah; - if (batteryChargeChanged) { - firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG; - } - dest.writeInt(firstToken); - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken) - + " deltaTime=" + deltaTime); - } - - if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) { - if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) { - if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int) deltaTime); - dest.writeInt((int) deltaTime); - } else { - if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime); - dest.writeLong(deltaTime); - } - } - if (batteryLevelIntChanged) { - dest.writeInt(batteryLevelInt); - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: batteryToken=0x" - + Integer.toHexString(batteryLevelInt) - + " batteryLevel=" + cur.batteryLevel - + " batteryTemp=" + cur.batteryTemperature - + " batteryVolt=" + (int) cur.batteryVoltage); - } - } - if (stateIntChanged) { - dest.writeInt(stateInt); - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: stateToken=0x" - + Integer.toHexString(stateInt) - + " batteryStatus=" + cur.batteryStatus - + " batteryHealth=" + cur.batteryHealth - + " batteryPlugType=" + cur.batteryPlugType - + " states=0x" + Integer.toHexString(cur.states)); - } - } - if (state2IntChanged) { - dest.writeInt(cur.states2); - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: states2=0x" - + Integer.toHexString(cur.states2)); - } - } - if (cur.wakelockTag != null || cur.wakeReasonTag != null) { - int wakeLockIndex; - int wakeReasonIndex; - if (cur.wakelockTag != null) { - wakeLockIndex = writeHistoryTag(cur.wakelockTag); - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx - + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string); - } - } else { - wakeLockIndex = 0xffff; - } - if (cur.wakeReasonTag != null) { - wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag); - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx - + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string); - } - } else { - wakeReasonIndex = 0xffff; - } - dest.writeInt((wakeReasonIndex << 16) | wakeLockIndex); - if (cur.wakelockTag != null - && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { - cur.wakelockTag.writeToParcel(dest, 0); - cur.tagsFirstOccurrence = true; - } - if (cur.wakeReasonTag != null - && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { - cur.wakeReasonTag.writeToParcel(dest, 0); - cur.tagsFirstOccurrence = true; - } - } - if (cur.eventCode != HistoryItem.EVENT_NONE) { - final int index = writeHistoryTag(cur.eventTag); - final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16); - dest.writeInt(codeAndIndex); - if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { - cur.eventTag.writeToParcel(dest, 0); - cur.tagsFirstOccurrence = true; - } - if (DEBUG) { - Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#" - + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":" - + cur.eventTag.string); - } - } - - cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails(); - if (includeStepDetails != 0) { - cur.stepDetails.writeToParcel(dest); - } - - if (batteryChargeChanged) { - if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah); - dest.writeInt(cur.batteryChargeUah); - } - dest.writeDouble(cur.modemRailChargeMah); - dest.writeDouble(cur.wifiRailChargeMah); - } - - private int buildBatteryLevelInt(HistoryItem h) { - return ((((int) h.batteryLevel) << 25) & 0xfe000000) - | ((((int) h.batteryTemperature) << 15) & 0x01ff8000) - | ((((int) h.batteryVoltage) << 1) & 0x00007ffe); - } - - private int buildStateInt(HistoryItem h) { - int plugType = 0; - if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_AC) != 0) { - plugType = 1; - } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) { - plugType = 2; - } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) { - plugType = 3; - } - return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK) - << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT) - | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK) - << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT) - | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK) - << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT) - | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK)); - } - - /** - * Returns the index for the specified tag. If this is the first time the tag is encountered - * while writing the current history buffer, the method returns - * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code> - */ - private int writeHistoryTag(HistoryTag tag) { - if (tag.string == null) { - Slog.wtfStack(TAG, "writeHistoryTag called with null name"); - } - - final int stringLength = tag.string.length(); - if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) { - Slog.e(TAG, "Long battery history tag: " + tag.string); - tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH); - } - - Integer idxObj = mHistoryTagPool.get(tag); - int idx; - if (idxObj != null) { - idx = idxObj; - if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { - mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG); - } - return idx; - } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) { - idx = mNextHistoryTagIdx; - HistoryTag key = new HistoryTag(); - key.setTo(tag); - tag.poolIdx = idx; - mHistoryTagPool.put(key, idx); - mNextHistoryTagIdx++; - - mNumHistoryTagChars += stringLength + 1; - if (mHistoryTags != null) { - mHistoryTags.put(idx, key); - } - return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG; - } else { - // Tag pool overflow: include the tag itself in the parcel - return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG; - } - } - - /** - * Don't allow any more batching in to the current history event. - */ - public void commitCurrentHistoryBatchLocked() { - mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; - } - - /** - * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} . - */ - public void writeHistory() { - if (mActiveFile == null) { - Slog.w(TAG, "writeHistory: no history file associated with this instance"); - return; - } - - Parcel p = Parcel.obtain(); - try { - final long start = SystemClock.uptimeMillis(); - writeHistoryBuffer(p); - if (DEBUG) { - Slog.d(TAG, "writeHistoryBuffer duration ms:" - + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize()); - } - writeParcelToFileLocked(p, mActiveFile); - } finally { - p.recycle(); - } - } - - /** - * Reads history buffer from a persisted Parcel. - */ - public void readHistoryBuffer(Parcel in) throws ParcelFormatException { - final int version = in.readInt(); - if (version != BatteryStatsHistory.VERSION) { - Slog.w("BatteryStats", "readHistoryBuffer: version got " + version - + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats"); - return; - } - - final long historyBaseTime = in.readLong(); - - mHistoryBuffer.setDataSize(0); - mHistoryBuffer.setDataPosition(0); - - int bufSize = in.readInt(); - int curPos = in.dataPosition(); - if (bufSize >= (mMaxHistoryBufferSize * 100)) { - throw new ParcelFormatException( - "File corrupt: history data buffer too large " + bufSize); - } else if ((bufSize & ~3) != bufSize) { - throw new ParcelFormatException( - "File corrupt: history data buffer not aligned " + bufSize); - } else { - if (DEBUG) { - Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize - + " bytes at " + curPos); - } - mHistoryBuffer.appendFrom(in, curPos, bufSize); - in.setDataPosition(curPos + bufSize); - } - - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** OLD mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - Slog.i(TAG, sb.toString()); - } - mHistoryBaseTimeMs = historyBaseTime; - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** NEW mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - Slog.i(TAG, sb.toString()); - } - - // We are just arbitrarily going to insert 1 minute from the sample of - // the last run until samples in this run. - if (mHistoryBaseTimeMs > 0) { - long oldnow = mClock.elapsedRealtime(); - mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1; - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** ADJUSTED mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - Slog.i(TAG, sb.toString()); - } - } - } - - private void writeHistoryBuffer(Parcel out) { - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** WRITING mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - sb.append(" mLastHistoryElapsedRealtimeMs: "); - TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb); - Slog.i(TAG, sb.toString()); - } - out.writeInt(BatteryStatsHistory.VERSION); - out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs); - out.writeInt(mHistoryBuffer.dataSize()); - if (DEBUG) { - Slog.i(TAG, "***************** WRITING HISTORY: " - + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition()); - } - out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize()); - } - - private void writeParcelToFileLocked(Parcel p, AtomicFile file) { - FileOutputStream fos = null; - mWriteLock.lock(); - try { - final long startTimeMs = SystemClock.uptimeMillis(); - fos = file.startWrite(); - fos.write(p.marshall()); - fos.flush(); - file.finishWrite(fos); - if (DEBUG) { - Slog.d(TAG, "writeParcelToFileLocked file:" + file.getBaseFile().getPath() - + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs) - + " bytes:" + p.dataSize()); - } - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( - "batterystats", SystemClock.uptimeMillis() - startTimeMs); - } catch (IOException e) { - Slog.w(TAG, "Error writing battery statistics", e); - file.failWrite(fos); - } finally { - mWriteLock.unlock(); - } - } - - /** - * Returns the total number of history tags in the tag pool. - */ - public int getHistoryStringPoolSize() { - return mHistoryTagPool.size(); - } - - /** - * Returns the total number of bytes occupied by the history tag pool. - */ - public int getHistoryStringPoolBytes() { - return mNumHistoryTagChars; - } - - /** - * Returns the string held by the requested history tag. - */ - public String getHistoryTagPoolString(int index) { - ensureHistoryTagArray(); - HistoryTag historyTag = mHistoryTags.get(index); - return historyTag != null ? historyTag.string : null; - } - - /** - * Returns the UID held by the requested history tag. - */ - public int getHistoryTagPoolUid(int index) { - ensureHistoryTagArray(); - HistoryTag historyTag = mHistoryTags.get(index); - return historyTag != null ? historyTag.uid : Process.INVALID_UID; - } - - private void ensureHistoryTagArray() { - if (mHistoryTags != null) { - return; - } - - mHistoryTags = new SparseArray<>(mHistoryTagPool.size()); - for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) { - mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG, - entry.getKey()); - } - } } diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 1bf878cb9119..de8b414c4b78 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -36,6 +36,7 @@ public class BatteryStatsHistoryIterator { public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) { mBatteryStatsHistory = history; + mBatteryStatsHistory.startIteratingHistory(); } /** @@ -230,11 +231,4 @@ public class BatteryStatsHistoryIterator { out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15); out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1); } - - /** - * Should be called when iteration is complete. - */ - public void close() { - mBatteryStatsHistory.finishIteratingHistory(); - } } diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java index c9e8f8f08229..5b6075ec54ce 100644 --- a/core/java/com/android/internal/util/FastDataOutput.java +++ b/core/java/com/android/internal/util/FastDataOutput.java @@ -59,7 +59,7 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable { /** * Values that have been "interned" by {@link #writeInternedUTF(String)}. */ - private final HashMap<String, Short> mStringRefs = new HashMap<>(); + private final HashMap<String, Integer> mStringRefs = new HashMap<>(); /** * @deprecated callers must specify {@code use4ByteSequence} so they make a @@ -256,7 +256,7 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable { * @see FastDataInput#readInternedUTF() */ public void writeInternedUTF(@NonNull String s) throws IOException { - Short ref = mStringRefs.get(s); + Integer ref = mStringRefs.get(s); if (ref != null) { writeShort(ref); } else { @@ -265,7 +265,7 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable { // We can only safely intern when we have remaining values; if we're // full we at least sent the string value above - ref = (short) mStringRefs.size(); + ref = mStringRefs.size(); if (ref < MAX_UNSIGNED_SHORT) { mStringRefs.put(s, ref); } diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index 95a4e1238e06..bc729f10d74c 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -1475,7 +1475,6 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); - contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 671e63493323..7f50204fb842 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -49,7 +49,7 @@ per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.co per-file *Zygote* = file:/ZYGOTE_OWNERS per-file core_jni_helpers.* = file:/ZYGOTE_OWNERS per-file fd_utils.* = file:/ZYGOTE_OWNERS -per-file Android.bp = file:platform/build/soong:/OWNERS +per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} per-file android_animation_* = file:/core/java/android/animation/OWNERS per-file android_app_admin_* = file:/core/java/android/app/admin/OWNERS per-file android_hardware_Usb* = file:/services/usb/OWNERS diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index d8522658d747..17cfbfc63f65 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -720,19 +720,22 @@ static jint util_texImage2D(JNIEnv *env, jclass clazz, jint target, jint level, jint internalformat, jobject bitmapObj, jint type, jint border) { graphics::Bitmap bitmap(env, bitmapObj); - AndroidBitmapInfo bitmapInfo = bitmap.getInfo(); + if (bitmap.isValid() && bitmap.getPixels() != nullptr) { + AndroidBitmapInfo bitmapInfo = bitmap.getInfo(); - if (internalformat < 0) { - internalformat = getInternalFormat(bitmapInfo.format); - } - if (type < 0) { - type = getType(bitmapInfo.format); - } + if (internalformat < 0) { + internalformat = getInternalFormat(bitmapInfo.format); + } + if (type < 0) { + type = getType(bitmapInfo.format); + } - if (checkInternalFormat(bitmapInfo.format, internalformat, type)) { - glTexImage2D(target, level, internalformat, bitmapInfo.width, bitmapInfo.height, border, - getPixelFormatFromInternalFormat(internalformat), type, bitmap.getPixels()); - return 0; + if (checkInternalFormat(bitmapInfo.format, internalformat, type)) { + glTexImage2D(target, level, internalformat, bitmapInfo.width, bitmapInfo.height, border, + getPixelFormatFromInternalFormat(internalformat), type, + bitmap.getPixels()); + return 0; + } } return -1; } @@ -741,19 +744,21 @@ static jint util_texSubImage2D(JNIEnv *env, jclass clazz, jint target, jint leve jint xoffset, jint yoffset, jobject bitmapObj, jint format, jint type) { graphics::Bitmap bitmap(env, bitmapObj); - AndroidBitmapInfo bitmapInfo = bitmap.getInfo(); - - int internalFormat = getInternalFormat(bitmapInfo.format); - if (format < 0) { - format = getPixelFormatFromInternalFormat(internalFormat); - if (format == GL_PALETTE8_RGBA8_OES) - return -1; // glCompressedTexSubImage2D() not supported - } + if (bitmap.isValid() && bitmap.getPixels() != nullptr) { + AndroidBitmapInfo bitmapInfo = bitmap.getInfo(); + + int internalFormat = getInternalFormat(bitmapInfo.format); + if (format < 0) { + format = getPixelFormatFromInternalFormat(internalFormat); + if (format == GL_PALETTE8_RGBA8_OES) + return -1; // glCompressedTexSubImage2D() not supported + } - if (checkInternalFormat(bitmapInfo.format, internalFormat, type)) { - glTexSubImage2D(target, level, xoffset, yoffset, bitmapInfo.width, bitmapInfo.height, - format, type, bitmap.getPixels()); - return 0; + if (checkInternalFormat(bitmapInfo.format, internalFormat, type)) { + glTexSubImage2D(target, level, xoffset, yoffset, bitmapInfo.width, bitmapInfo.height, + format, type, bitmap.getPixels()); + return 0; + } } return -1; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 9ae16304b6c8..bc299fd0f1fe 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -820,14 +820,6 @@ static void nativeSetStretchEffect(JNIEnv* env, jclass clazz, jlong transactionO transaction->setStretchEffect(ctrl, stretch); } -static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj, - jlong nativeObject, jint w, jint h) { - auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - - SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); - transaction->setSize(ctrl, w, h); -} - static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint flags, jint mask) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -2121,6 +2113,20 @@ static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceContr return surface->getLayerId(); } +static void nativeSetDefaultApplyToken(JNIEnv* env, jclass clazz, jobject applyToken) { + sp<IBinder> token(ibinderForJavaObject(env, applyToken)); + if (token == nullptr) { + ALOGE("Null apply token provided."); + return; + } + SurfaceComposerClient::Transaction::setDefaultApplyToken(token); +} + +static jobject nativeGetDefaultApplyToken(JNIEnv* env, jclass clazz) { + sp<IBinder> token = SurfaceComposerClient::Transaction::getDefaultApplyToken(); + return javaObjectForIBinder(env, token); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { @@ -2163,8 +2169,6 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetPosition }, {"nativeSetScale", "(JJFF)V", (void*)nativeSetScale }, - {"nativeSetSize", "(JJII)V", - (void*)nativeSetSize }, {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V", (void*)nativeSetTransparentRegionHint }, {"nativeSetDamageRegion", "(JJLandroid/graphics/Region;)V", @@ -2343,6 +2347,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*) nativeSanitize }, {"nativeSetDestinationFrame", "(JJIIII)V", (void*)nativeSetDestinationFrame }, + {"nativeSetDefaultApplyToken", "(Landroid/os/IBinder;)V", + (void*)nativeSetDefaultApplyToken }, + {"nativeGetDefaultApplyToken", "()Landroid/os/IBinder;", + (void*)nativeGetDefaultApplyToken }, // clang-format on }; diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml index ca0373773577..776a35d15ef0 100644 --- a/core/res/res/layout/floating_popup_container.xml +++ b/core/res/res/layout/floating_popup_container.xml @@ -16,6 +16,7 @@ */ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/floating_popup_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="0dp" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b59cf7fd3bcb..116b15051e7d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2392,9 +2392,6 @@ <!-- The list of supported dream complications --> <integer-array name="config_supportedDreamComplications"> </integer-array> - <!-- The list of dream complications which should be enabled by default --> - <integer-array name="config_dreamComplicationsEnabledByDefault"> - </integer-array> <!-- Are we allowed to dream while not plugged in? --> <bool name="config_dreamsEnabledOnBattery">false</bool> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b7da6ae31ce3..7a2c866375d2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5422,8 +5422,10 @@ <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=25] --> <string name="search_language_hint">Type language name</string> - <!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] --> + <!-- List section subheader for the language picker, containing a list of suggested languages [CHAR LIMIT=30] --> <string name="language_picker_section_suggested">Suggested</string> + <!-- "List section subheader for the language picker, containing a list of suggested regions available for that language [CHAR LIMIT=30] --> + <string name="language_picker_regions_section_suggested">Suggested</string> <!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] --> <string name="language_picker_section_suggested_bilingual">Suggested languages</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d60fc209ebc1..3afa6efab2a6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2228,7 +2228,6 @@ <java-symbol type="string" name="config_dreamsDefaultComponent" /> <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" /> <java-symbol type="array" name="config_supportedDreamComplications" /> - <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="string" name="config_loggable_dream_prefix" /> @@ -3141,6 +3140,7 @@ <java-symbol type="string" name="language_picker_section_all" /> <java-symbol type="string" name="region_picker_section_all" /> <java-symbol type="string" name="language_picker_section_suggested" /> + <java-symbol type="string" name="language_picker_regions_section_suggested" /> <java-symbol type="string" name="language_picker_section_suggested_bilingual" /> <java-symbol type="string" name="region_picker_section_suggested_bilingual" /> <java-symbol type="string" name="language_selection_title" /> diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 0a5a4d5a9adb..993ecf66c25b 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -221,15 +221,15 @@ public class ObjectPoolTests { @Test public void testRecyclePauseActivityItemItem() { - PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false); - PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true); + PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false, false); + PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true, true); assertNotSame(item, emptyItem); assertFalse(item.equals(emptyItem)); item.recycle(); assertEquals(item, emptyItem); - PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true); + PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true, true); assertSame(item, item2); assertFalse(item2.equals(emptyItem)); } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index e9bbdbee576c..b292d7dfafe2 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -234,7 +234,8 @@ public class TransactionParcelTests { public void testPause() { // Write to parcel PauseActivityItem item = PauseActivityItem.obtain(true /* finished */, - true /* userLeaving */, 135 /* configChanges */, true /* dontReport */); + true /* userLeaving */, 135 /* configChanges */, true /* dontReport */, + true /* autoEnteringPip */); writeAndPrepareForReading(item); // Read from parcel and assert diff --git a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java index c6f592447c22..2d3ed9510534 100644 --- a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java +++ b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java @@ -16,13 +16,12 @@ package android.widget; -import static com.android.internal.widget.floatingtoolbar.FloatingToolbar.FLOATING_TOOLBAR_TAG; - import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.content.res.Resources; import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; @@ -33,25 +32,27 @@ import com.android.internal.R; final class FloatingToolbarUtils { private final UiDevice mDevice; + private static final BySelector TOOLBAR_CONTAINER_SELECTOR = + By.res("android", "floating_popup_container"); FloatingToolbarUtils() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); } void waitForFloatingToolbarPopup() { - mDevice.wait(Until.findObject(By.desc(FLOATING_TOOLBAR_TAG)), 500); + mDevice.wait(Until.findObject(TOOLBAR_CONTAINER_SELECTOR), 500); } void assertFloatingToolbarIsDisplayed() { waitForFloatingToolbarPopup(); - assertThat(mDevice.hasObject(By.desc(FLOATING_TOOLBAR_TAG))).isTrue(); + assertThat(mDevice.hasObject(TOOLBAR_CONTAINER_SELECTOR)).isTrue(); } void assertFloatingToolbarContainsItem(String itemLabel) { waitForFloatingToolbarPopup(); assertWithMessage("Expected to find item labelled [" + itemLabel + "]") .that(mDevice.hasObject( - By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel)))) + TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(itemLabel)))) .isTrue(); } @@ -59,14 +60,14 @@ final class FloatingToolbarUtils { waitForFloatingToolbarPopup(); assertWithMessage("Expected to not find item labelled [" + itemLabel + "]") .that(mDevice.hasObject( - By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel)))) + TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(itemLabel)))) .isFalse(); } void assertFloatingToolbarContainsItemAtIndex(String itemLabel, int index) { waitForFloatingToolbarPopup(); assertWithMessage("Expected to find item labelled [" + itemLabel + "] at index " + index) - .that(mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + .that(mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObjects(By.clickable(true)) .get(index) .getChildren() @@ -77,7 +78,7 @@ final class FloatingToolbarUtils { void clickFloatingToolbarItem(String label) { waitForFloatingToolbarPopup(); - mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObject(By.text(label)) .click(); } @@ -85,13 +86,13 @@ final class FloatingToolbarUtils { void clickFloatingToolbarOverflowItem(String label) { // TODO: There might be a benefit to combining this with "clickFloatingToolbarItem" method. waitForFloatingToolbarPopup(); - mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObject(By.desc(str(R.string.floating_toolbar_open_overflow_description))) .click(); mDevice.wait( - Until.findObject(By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(label))), + Until.findObject(TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(label))), 1000); - mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObject(By.text(label)) .click(); } diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index f4a6f025074e..613eddd30c8a 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -298,8 +298,8 @@ public class ActivityThreadClientTest { private void pauseActivity(ActivityClientRecord r) { mThread.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, null /* pendingActions */, - "test"); + false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */, + null /* pendingActions */, "test"); } private void stopActivity(ActivityClientRecord r) { diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 03d22ac44f6a..748032b9db62 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2065,6 +2065,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-108248992": { + "message": "Defer transition ready for TaskFragmentTransaction=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "-106400104": { "message": "Preload recents with %s", "level": "DEBUG", @@ -2113,6 +2119,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "-79016993": { + "message": "Continue transition ready for TaskFragmentTransaction=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "-70719599": { "message": "Unregister remote animations for organizer=%s uid=%d pid=%d", "level": "VERBOSE", diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index f8c015f28a50..8e2a59cd848a 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -39,7 +39,11 @@ <axis tag="wdth" stylevalue="100" /> <axis tag="wght" stylevalue="300" /> </font> - <font weight="400" style="normal">RobotoStatic-Regular.ttf</font> + <font weight="400" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="400" /> + </font> <font weight="500" style="normal">Roboto-Regular.ttf <axis tag="ital" stylevalue="0" /> <axis tag="wdth" stylevalue="100" /> diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index 8533a5994d33..b0dab90b6add 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -16,92 +16,98 @@ --> <!-- Layout for TvPipMenuView --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/tv_pip_menu" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center|top"> + android:id="@+id/tv_pip_menu" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center|top"> <!-- Matches the PiP app content --> - <View + <FrameLayout android:id="@+id/tv_pip" android:layout_width="0dp" android:layout_height="0dp" - android:alpha="0" - android:background="@color/tv_pip_menu_background" android:layout_marginTop="@dimen/pip_menu_outer_space" android:layout_marginStart="@dimen/pip_menu_outer_space" - android:layout_marginEnd="@dimen/pip_menu_outer_space"/> - - <ScrollView - android:id="@+id/tv_pip_menu_scroll" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignTop="@+id/tv_pip" - android:layout_alignStart="@+id/tv_pip" - android:layout_alignEnd="@+id/tv_pip" - android:layout_alignBottom="@+id/tv_pip" - android:scrollbars="none" - android:visibility="gone"/> - - <HorizontalScrollView - android:id="@+id/tv_pip_menu_horizontal_scroll" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignTop="@+id/tv_pip" - android:layout_alignStart="@+id/tv_pip" - android:layout_alignEnd="@+id/tv_pip" - android:layout_alignBottom="@+id/tv_pip" - android:scrollbars="none"> - - <LinearLayout - android:id="@+id/tv_pip_menu_action_buttons" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:alpha="0"> + android:layout_marginEnd="@dimen/pip_menu_outer_space"> - <Space - android:layout_width="@dimen/pip_menu_button_wrapper_margin" - android:layout_height="@dimen/pip_menu_button_wrapper_margin"/> - - <com.android.wm.shell.common.TvWindowMenuActionButton - android:id="@+id/tv_pip_menu_fullscreen_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_fullscreen_white" - android:text="@string/pip_fullscreen" /> + <View + android:id="@+id/tv_pip_menu_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/tv_pip_menu_background" + android:alpha="0"/> - <com.android.wm.shell.common.TvWindowMenuActionButton - android:id="@+id/tv_pip_menu_close_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_close_white" - android:text="@string/pip_close" /> + <View + android:id="@+id/tv_pip_menu_dim_layer" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/tv_pip_menu_dim_layer" + android:alpha="0"/> - <!-- More TvPipMenuActionButtons may be added here at runtime. --> + <ScrollView + android:id="@+id/tv_pip_menu_scroll" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="none" + android:visibility="gone"/> - <com.android.wm.shell.common.TvWindowMenuActionButton - android:id="@+id/tv_pip_menu_move_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_move_white" - android:text="@string/pip_move" /> + <HorizontalScrollView + android:id="@+id/tv_pip_menu_horizontal_scroll" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="none"> - <com.android.wm.shell.common.TvWindowMenuActionButton - android:id="@+id/tv_pip_menu_expand_button" + <LinearLayout + android:id="@+id/tv_pip_menu_action_buttons" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:src="@drawable/pip_ic_collapse" - android:visibility="gone" - android:text="@string/pip_collapse" /> - - <Space - android:layout_width="@dimen/pip_menu_button_wrapper_margin" - android:layout_height="@dimen/pip_menu_button_wrapper_margin"/> - - </LinearLayout> - </HorizontalScrollView> + android:orientation="horizontal" + android:alpha="0"> + + <Space + android:layout_width="@dimen/pip_menu_button_wrapper_margin" + android:layout_height="@dimen/pip_menu_button_wrapper_margin"/> + + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_fullscreen_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_fullscreen_white" + android:text="@string/pip_fullscreen" /> + + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_close_white" + android:text="@string/pip_close" /> + + <!-- More TvPipMenuActionButtons may be added here at runtime. --> + + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_move_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_move_white" + android:text="@string/pip_move" /> + + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_collapse" + android:visibility="gone" + android:text="@string/pip_collapse" /> + + <Space + android:layout_width="@dimen/pip_menu_button_wrapper_margin" + android:layout_height="@dimen/pip_menu_button_wrapper_margin"/> + + </LinearLayout> + </HorizontalScrollView> + </FrameLayout> + <!-- Frame around the content, just overlapping the corners to make them round --> <View android:id="@+id/tv_pip_border" android:layout_width="0dp" @@ -111,6 +117,7 @@ android:layout_marginEnd="@dimen/pip_menu_outer_space_frame" android:background="@drawable/tv_pip_menu_border"/> + <!-- Temporarily extending the background to show an edu text hint for opening the menu --> <FrameLayout android:id="@+id/tv_pip_menu_edu_text_container" android:layout_width="match_parent" @@ -138,6 +145,7 @@ android:textAppearance="@style/TvPipEduText"/> </FrameLayout> + <!-- Frame around the PiP content + edu text hint - used to highlight open menu --> <View android:id="@+id/tv_pip_menu_frame" android:layout_width="match_parent" @@ -145,7 +153,8 @@ android:layout_margin="@dimen/pip_menu_outer_space_frame" android:background="@drawable/tv_pip_menu_border"/> - <com.android.wm.shell.common.TvWindowMenuActionButton + <!-- Move menu --> + <com.android.wm.shell.pip.tv.TvPipMenuActionButton android:id="@+id/tv_pip_menu_done_button" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml index 3e71c1010278..e6933ca3fce6 100644 --- a/libs/WindowManager/Shell/res/values/colors_tv.xml +++ b/libs/WindowManager/Shell/res/values/colors_tv.xml @@ -25,6 +25,7 @@ <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color> <color name="tv_pip_menu_focus_border">#E8EAED</color> + <color name="tv_pip_menu_dim_layer">#990E0E0F</color> <color name="tv_pip_menu_background">#1E232C</color> <color name="tv_pip_edu_text">#99D2E3FC</color> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java new file mode 100644 index 000000000000..cc4db933ec9f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MTRANS_X; +import static android.graphics.Matrix.MTRANS_Y; + +import android.annotation.CallSuper; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.Choreographer; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; + +/** + * Wrapper to handle the ActivityEmbedding animation update in one + * {@link SurfaceControl.Transaction}. + */ +class ActivityEmbeddingAnimationAdapter { + + /** + * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer. + */ + private static final int LAYER_NO_OVERRIDE = -1; + + final Animation mAnimation; + final TransitionInfo.Change mChange; + final SurfaceControl mLeash; + + final Transformation mTransformation = new Transformation(); + final float[] mMatrix = new float[9]; + final float[] mVecs = new float[4]; + final Rect mRect = new Rect(); + private boolean mIsFirstFrame = true; + private int mOverrideLayer = LAYER_NO_OVERRIDE; + + ActivityEmbeddingAnimationAdapter(@NonNull Animation animation, + @NonNull TransitionInfo.Change change) { + this(animation, change, change.getLeash()); + } + + /** + * @param leash the surface to animate, which is not necessary the same as + * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example. + */ + ActivityEmbeddingAnimationAdapter(@NonNull Animation animation, + @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) { + mAnimation = animation; + mChange = change; + mLeash = leash; + } + + /** + * Surface layer to be set at the first frame of the animation. We will not set the layer if it + * is set to {@link #LAYER_NO_OVERRIDE}. + */ + final void overrideLayer(int layer) { + mOverrideLayer = layer; + } + + /** Called on frame update. */ + final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { + if (mIsFirstFrame) { + t.show(mLeash); + if (mOverrideLayer != LAYER_NO_OVERRIDE) { + t.setLayer(mLeash, mOverrideLayer); + } + mIsFirstFrame = false; + } + + // Extract the transformation to the current time. + mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()), + mTransformation); + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + onAnimationUpdateInner(t); + } + + /** To be overridden by subclasses to adjust the animation surface change. */ + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + final Point offset = mChange.getEndRelOffset(); + mTransformation.getMatrix().postTranslate(offset.x, offset.y); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + // Get current animation position. + final int positionX = Math.round(mMatrix[MTRANS_X]); + final int positionY = Math.round(mMatrix[MTRANS_Y]); + // The exiting surface starts at position: Change#getEndRelOffset() and moves with + // positionX varying. Offset our crop region by the amount we have slided so crop + // regions stays exactly on the original container in split. + final int cropOffsetX = offset.x - positionX; + final int cropOffsetY = offset.y - positionY; + final Rect cropRect = new Rect(); + cropRect.set(mChange.getEndAbsBounds()); + // Because window crop uses absolute position. + cropRect.offsetTo(0, 0); + cropRect.offset(cropOffsetX, cropOffsetY); + t.setCrop(mLeash, cropRect); + } + + /** Called after animation finished. */ + @CallSuper + void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { + onAnimationUpdate(t, mAnimation.getDuration()); + } + + final long getDurationHint() { + return mAnimation.computeDurationHint(); + } + + /** + * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to + * animate together as one. This adapter will offset the animation leash to make the animate of + * two windows look like a single window. + */ + static class SplitAdapter extends ActivityEmbeddingAnimationAdapter { + private final boolean mIsLeftHalf; + private final int mWholeAnimationWidth; + + /** + * @param isLeftHalf whether this is the left half of the animation. + * @param wholeAnimationWidth the whole animation windows width. + */ + SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change, + boolean isLeftHalf, int wholeAnimationWidth) { + super(animation, change); + mIsLeftHalf = isLeftHalf; + mWholeAnimationWidth = wholeAnimationWidth; + if (wholeAnimationWidth == 0) { + throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth"); + } + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + final Point offset = mChange.getEndRelOffset(); + float posX = offset.x; + final float posY = offset.y; + // This window is half of the whole animation window. Offset left/right to make it + // look as one with the other half. + mTransformation.getMatrix().getValues(mMatrix); + final int changeWidth = mChange.getEndAbsBounds().width(); + final float scaleX = mMatrix[MSCALE_X]; + final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2; + final float curOffset = changeWidth * (1 - scaleX) / 2; + final float offsetDiff = totalOffset - curOffset; + if (mIsLeftHalf) { + posX += offsetDiff; + } else { + posX -= offsetDiff; + } + mTransformation.getMatrix().postTranslate(posX, posY); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + } + + /** + * Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has + * size change. + */ + static class SnapshotAdapter extends ActivityEmbeddingAnimationAdapter { + + SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl snapshotLeash) { + super(animation, change, snapshotLeash); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + // Snapshot should always be placed at the top left of the animation leash. + mTransformation.getMatrix().postTranslate(0, 0); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + + @Override + void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { + super.onAnimationEnd(t); + // Remove the screenshot leash after animation is finished. + t.remove(mLeash); + } + } + + /** + * Should be used for the animation of the {@link TransitionInfo.Change} that has size change. + */ + static class BoundsChangeAdapter extends ActivityEmbeddingAnimationAdapter { + + BoundsChangeAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change) { + super(animation, change); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + final Point offset = mChange.getEndRelOffset(); + mTransformation.getMatrix().postTranslate(offset.x, offset.y); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + + // The following applies an inverse scale to the clip-rect so that it crops "after" the + // scale instead of before. + mVecs[1] = mVecs[2] = 0; + mVecs[0] = mVecs[3] = 1; + mTransformation.getMatrix().mapVectors(mVecs); + mVecs[0] = 1.f / mVecs[0]; + mVecs[3] = 1.f / mVecs[3]; + final Rect clipRect = mTransformation.getClipRect(); + mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f); + mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f); + mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f); + mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f); + t.setCrop(mLeash, mRect); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java new file mode 100644 index 000000000000..7e0795d11153 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Log; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.common.ScreenshotUtils; +import com.android.wm.shell.transition.Transitions; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +/** To run the ActivityEmbedding animations. */ +class ActivityEmbeddingAnimationRunner { + + private static final String TAG = "ActivityEmbeddingAnimR"; + + private final ActivityEmbeddingController mController; + @VisibleForTesting + final ActivityEmbeddingAnimationSpec mAnimationSpec; + + ActivityEmbeddingAnimationRunner(@NonNull Context context, + @NonNull ActivityEmbeddingController controller) { + mController = controller; + mAnimationSpec = new ActivityEmbeddingAnimationSpec(context); + } + + /** Creates and starts animation for ActivityEmbedding transition. */ + void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + final Animator animator = createAnimator(info, startTransaction, finishTransaction, + () -> mController.onAnimationFinished(transition)); + startTransaction.apply(); + animator.start(); + } + + /** + * Sets transition animation scale settings value. + * @param scale The setting value of transition animation scale. + */ + void setAnimScaleSetting(float scale) { + mAnimationSpec.setAnimScaleSetting(scale); + } + + /** Creates the animator for the given {@link TransitionInfo}. */ + @VisibleForTesting + @NonNull + Animator createAnimator(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Runnable animationFinishCallback) { + final List<ActivityEmbeddingAnimationAdapter> adapters = + createAnimationAdapters(info, startTransaction); + long duration = 0; + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + duration = Math.max(duration, adapter.getDurationHint()); + } + final ValueAnimator animator = ValueAnimator.ofFloat(0, 1); + animator.setDuration(duration); + animator.addUpdateListener((anim) -> { + // Update all adapters in the same transaction. + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + adapter.onAnimationUpdate(t, animator.getCurrentPlayTime()); + } + t.apply(); + }); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + adapter.onAnimationEnd(t); + } + t.apply(); + animationFinishCallback.run(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + return animator; + } + + /** + * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window + * changes. + */ + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getMode() == TRANSIT_CHANGE + && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { + return createChangeAnimationAdapters(info, startTransaction); + } + } + if (Transitions.isClosingType(info.getType())) { + return createCloseAnimationAdapters(info); + } + return createOpenAnimationAdapters(info); + } + + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, true /* isOpening */, + mAnimationSpec::loadOpenAnimation); + } + + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, false /* isOpening */, + mAnimationSpec::loadCloseAnimation); + } + + /** + * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition. + * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type. + */ + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( + @NonNull TransitionInfo info, boolean isOpening, + @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) { + // We need to know if the change window is only a partial of the whole animation screen. + // If so, we will need to adjust it to make the whole animation screen looks like one. + final List<TransitionInfo.Change> openingChanges = new ArrayList<>(); + final List<TransitionInfo.Change> closingChanges = new ArrayList<>(); + final Rect openingWholeScreenBounds = new Rect(); + final Rect closingWholeScreenBounds = new Rect(); + for (TransitionInfo.Change change : info.getChanges()) { + final Rect bounds = new Rect(change.getEndAbsBounds()); + final Point offset = change.getEndRelOffset(); + bounds.offsetTo(offset.x, offset.y); + if (Transitions.isOpeningType(change.getMode())) { + openingChanges.add(change); + openingWholeScreenBounds.union(bounds); + } else { + closingChanges.add(change); + closingWholeScreenBounds.union(bounds); + } + } + + // For OPEN transition, open windows should be above close windows. + // For CLOSE transition, open windows should be below close windows. + int offsetLayer = TYPE_LAYER_OFFSET; + final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); + for (TransitionInfo.Change change : openingChanges) { + final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( + change, animationProvider, openingWholeScreenBounds); + if (isOpening) { + adapter.overrideLayer(offsetLayer++); + } + adapters.add(adapter); + } + for (TransitionInfo.Change change : closingChanges) { + final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( + change, animationProvider, closingWholeScreenBounds); + if (!isOpening) { + adapter.overrideLayer(offsetLayer++); + } + adapters.add(adapter); + } + return adapters; + } + + @NonNull + private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( + @NonNull TransitionInfo.Change change, + @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider, + @NonNull Rect wholeAnimationBounds) { + final Animation animation = animationProvider.apply(change, wholeAnimationBounds); + final Rect bounds = new Rect(change.getEndAbsBounds()); + final Point offset = change.getEndRelOffset(); + bounds.offsetTo(offset.x, offset.y); + if (bounds.left == wholeAnimationBounds.left + && bounds.right != wholeAnimationBounds.right) { + // This is the left split of the whole animation window. + return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change, + true /* isLeftHalf */, wholeAnimationBounds.width()); + } else if (bounds.left != wholeAnimationBounds.left + && bounds.right == wholeAnimationBounds.right) { + // This is the right split of the whole animation window. + return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change, + false /* isLeftHalf */, wholeAnimationBounds.width()); + } + // Open/close window that fills the whole animation. + return new ActivityEmbeddingAnimationAdapter(animation, change); + } + + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters( + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getMode() == TRANSIT_CHANGE + && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { + // This is the window with bounds change. + final WindowContainerToken parentToken = change.getParent(); + final Rect parentBounds; + if (parentToken != null) { + TransitionInfo.Change parentChange = info.getChange(parentToken); + parentBounds = parentChange != null + ? parentChange.getEndAbsBounds() + : change.getEndAbsBounds(); + } else { + parentBounds = change.getEndAbsBounds(); + } + final Animation[] animations = + mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds); + // Adapter for the starting screenshot leash. + final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction); + if (screenshotLeash != null) { + // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd + adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( + animations[0], change, screenshotLeash)); + } else { + Log.e(TAG, "Failed to take screenshot for change=" + change); + } + // Adapter for the ending bounds changed leash. + adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( + animations[1], change)); + continue; + } + + // These are the other windows that don't have bounds change in the same transition. + final Animation animation; + if (!TransitionInfo.isIndependent(change, info)) { + // No-op if it will be covered by the changing parent window. + animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); + } else if (Transitions.isClosingType(change.getMode())) { + animation = mAnimationSpec.createChangeBoundsCloseAnimation(change); + } else { + animation = mAnimationSpec.createChangeBoundsOpenAnimation(change); + } + adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change)); + } + return adapters; + } + + /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */ + @Nullable + private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startTransaction) { + final Rect cropBounds = new Rect(change.getStartAbsBounds()); + cropBounds.offsetTo(0, 0); + return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds, + Integer.MAX_VALUE); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java new file mode 100644 index 000000000000..6f06f28caff2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.AnimationUtils; +import android.view.animation.ClipRectAnimation; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; + +import com.android.internal.R; +import com.android.internal.policy.TransitionAnimation; +import com.android.wm.shell.transition.Transitions; + +/** Animation spec for ActivityEmbedding transition. */ +// TODO(b/206557124): provide an easier way to customize animation +class ActivityEmbeddingAnimationSpec { + + private static final String TAG = "ActivityEmbeddingAnimSpec"; + private static final int CHANGE_ANIMATION_DURATION = 517; + private static final int CHANGE_ANIMATION_FADE_DURATION = 80; + private static final int CHANGE_ANIMATION_FADE_OFFSET = 30; + + private final Context mContext; + private final TransitionAnimation mTransitionAnimation; + private final Interpolator mFastOutExtraSlowInInterpolator; + private final LinearInterpolator mLinearInterpolator; + private float mTransitionAnimationScaleSetting; + + ActivityEmbeddingAnimationSpec(@NonNull Context context) { + mContext = context; + mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG); + mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.fast_out_extra_slow_in); + mLinearInterpolator = new LinearInterpolator(); + } + + /** + * Sets transition animation scale settings value. + * @param scale The setting value of transition animation scale. + */ + void setAnimScaleSetting(float scale) { + mTransitionAnimationScaleSetting = scale; + } + + /** For window that doesn't need to be animated. */ + @NonNull + static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) { + // Noop but just keep the window showing/hiding. + final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f; + return new AlphaAnimation(alpha, alpha); + } + + /** Animation for window that is opening in a change transition. */ + @NonNull + Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) { + final Rect bounds = change.getEndAbsBounds(); + final Point offset = change.getEndRelOffset(); + // The window will be animated in from left or right depends on its position. + final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width(); + + // The position should be 0-based as we will post translate in + // ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0); + animation.setInterpolator(mFastOutExtraSlowInInterpolator); + animation.setDuration(CHANGE_ANIMATION_DURATION); + animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } + + /** Animation for window that is closing in a change transition. */ + @NonNull + Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) { + final Rect bounds = change.getEndAbsBounds(); + final Point offset = change.getEndRelOffset(); + // The window will be animated out to left or right depends on its position. + final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width(); + + // The position should be 0-based as we will post translate in + // ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Animation animation = new TranslateAnimation(0, endLeft, 0, 0); + animation.setInterpolator(mFastOutExtraSlowInInterpolator); + animation.setDuration(CHANGE_ANIMATION_DURATION); + animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } + + /** + * Animation for window that is changing (bounds change) in a change transition. + * @return the return array always has two elements. The first one is for the start leash, and + * the second one is for the end leash. + */ + @NonNull + Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change, + @NonNull Rect parentBounds) { + // Both start bounds and end bounds are in screen coordinates. We will post translate + // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Rect startBounds = change.getStartAbsBounds(); + final Rect endBounds = change.getEndAbsBounds(); + float scaleX = ((float) startBounds.width()) / endBounds.width(); + float scaleY = ((float) startBounds.height()) / endBounds.height(); + // Start leash is a child of the end leash. Reverse the scale so that the start leash won't + // be scaled up with its parent. + float startScaleX = 1.f / scaleX; + float startScaleY = 1.f / scaleY; + + // The start leash will be fade out. + final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */); + final Animation startAlpha = new AlphaAnimation(1f, 0f); + startAlpha.setInterpolator(mLinearInterpolator); + startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION); + startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET); + startSet.addAnimation(startAlpha); + final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY, + startScaleY); + startScale.setInterpolator(mFastOutExtraSlowInInterpolator); + startScale.setDuration(CHANGE_ANIMATION_DURATION); + startSet.addAnimation(startScale); + startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), + endBounds.height()); + startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); + + // The end leash will be moved into the end position while scaling. + final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */); + endSet.setInterpolator(mFastOutExtraSlowInInterpolator); + final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1); + endScale.setDuration(CHANGE_ANIMATION_DURATION); + endSet.addAnimation(endScale); + // The position should be 0-based as we will post translate in + // ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0, + 0, 0); + endTranslate.setDuration(CHANGE_ANIMATION_DURATION); + endSet.addAnimation(endTranslate); + // The end leash is resizing, we should update the window crop based on the clip rect. + final Rect startClip = new Rect(startBounds); + final Rect endClip = new Rect(endBounds); + startClip.offsetTo(0, 0); + endClip.offsetTo(0, 0); + final Animation clipAnim = new ClipRectAnimation(startClip, endClip); + clipAnim.setDuration(CHANGE_ANIMATION_DURATION); + endSet.addAnimation(clipAnim); + endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(), + parentBounds.height()); + endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); + + return new Animation[]{startSet, endSet}; + } + + @NonNull + Animation loadOpenAnimation(@NonNull TransitionInfo.Change change, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = Transitions.isOpeningType(change.getMode()); + final Animation animation; + // TODO(b/207070762): + // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit + // 2. Implement edgeExtension version + animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter + ? R.anim.task_fragment_open_enter + : R.anim.task_fragment_open_exit); + final Rect bounds = change.getEndAbsBounds(); + animation.initialize(bounds.width(), bounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } + + @NonNull + Animation loadCloseAnimation(@NonNull TransitionInfo.Change change, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = Transitions.isOpeningType(change.getMode()); + final Animation animation; + // TODO(b/207070762): + // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit + // 2. Implement edgeExtension version + animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter + ? R.anim.task_fragment_close_enter + : R.anim.task_fragment_close_exit); + final Rect bounds = change.getEndAbsBounds(); + animation.initialize(bounds.width(), bounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index b305897b77ae..e0004fcaa060 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -18,8 +18,11 @@ package com.android.wm.shell.activityembedding; import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static java.util.Objects.requireNonNull; + import android.content.Context; import android.os.IBinder; +import android.util.ArrayMap; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -28,6 +31,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -37,15 +41,37 @@ import com.android.wm.shell.transition.Transitions; public class ActivityEmbeddingController implements Transitions.TransitionHandler { private final Context mContext; - private final Transitions mTransitions; - - public ActivityEmbeddingController(Context context, ShellInit shellInit, - Transitions transitions) { - mContext = context; - mTransitions = transitions; - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - shellInit.addInitCallback(this::onInit, this); - } + @VisibleForTesting + final Transitions mTransitions; + @VisibleForTesting + final ActivityEmbeddingAnimationRunner mAnimationRunner; + + /** + * Keeps track of the currently-running transition callback associated with each transition + * token. + */ + private final ArrayMap<IBinder, Transitions.TransitionFinishCallback> mTransitionCallbacks = + new ArrayMap<>(); + + private ActivityEmbeddingController(@NonNull Context context, @NonNull ShellInit shellInit, + @NonNull Transitions transitions) { + mContext = requireNonNull(context); + mTransitions = requireNonNull(transitions); + mAnimationRunner = new ActivityEmbeddingAnimationRunner(context, this); + + shellInit.addInitCallback(this::onInit, this); + } + + /** + * Creates {@link ActivityEmbeddingController}, returns {@code null} if the feature is not + * supported. + */ + @Nullable + public static ActivityEmbeddingController create(@NonNull Context context, + @NonNull ShellInit shellInit, @NonNull Transitions transitions) { + return Transitions.ENABLE_SHELL_TRANSITIONS + ? new ActivityEmbeddingController(context, shellInit, transitions) + : null; } /** Registers to handle transitions. */ @@ -66,9 +92,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle } } - // TODO(b/207070762) Implement AE animation. - startTransaction.apply(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + // Start ActivityEmbedding animation. + mTransitionCallbacks.put(transition, finishCallback); + mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction); return true; } @@ -79,6 +105,21 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return null; } + @Override + public void setAnimScaleSetting(float scale) { + mAnimationRunner.setAnimScaleSetting(scale); + } + + /** Called when the animation is finished. */ + void onAnimationFinished(@NonNull IBinder transition) { + final Transitions.TransitionFinishCallback callback = + mTransitionCallbacks.remove(transition); + if (callback == null) { + throw new IllegalStateException("No finish callback found"); + } + callback.onTransitionFinished(null /* wct */, null /* wctCB */); + } + private static boolean isEmbedded(@NonNull TransitionInfo.Change change) { return (change.getFlags() & FLAG_IS_EMBEDDED) != 0; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 2c02006c8ca5..99b8885acdef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -821,7 +821,7 @@ public class Bubble implements BubbleViewProvider { /** * Description of current bubble state. */ - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + public void dump(@NonNull PrintWriter pw) { pw.print("key: "); pw.println(mKey); pw.print(" showInShade: "); pw.println(showInShade()); pw.print(" showDot: "); pw.println(showDot()); @@ -831,7 +831,7 @@ public class Bubble implements BubbleViewProvider { pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); if (mExpandedView != null) { - mExpandedView.dump(pw, args); + mExpandedView.dump(pw); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index de26b54971ca..dcbb272feab8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -72,7 +72,6 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.Log; import android.util.Pair; -import android.util.Slog; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; @@ -100,6 +99,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -159,6 +159,7 @@ public class BubbleController implements ConfigurationChangeListener { private final TaskViewTransitions mTaskViewTransitions; private final SyncTransactionQueue mSyncQueue; private final ShellController mShellController; + private final ShellCommandHandler mShellCommandHandler; // Used to post to main UI thread private final ShellExecutor mMainExecutor; @@ -229,6 +230,7 @@ public class BubbleController implements ConfigurationChangeListener { public BubbleController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, @@ -252,6 +254,7 @@ public class BubbleController implements ConfigurationChangeListener { TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { mContext = context; + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mLauncherApps = launcherApps; mBarService = statusBarService == null @@ -431,6 +434,7 @@ public class BubbleController implements ConfigurationChangeListener { mCurrentProfiles = userProfiles; mShellController.addConfigurationChangeListener(this); + mShellCommandHandler.addDumpCallback(this::dump, this); } @VisibleForTesting @@ -538,7 +542,6 @@ public class BubbleController implements ConfigurationChangeListener { if (mNotifEntryToExpandOnShadeUnlock != null) { expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock); - mNotifEntryToExpandOnShadeUnlock = null; } updateStack(); @@ -925,15 +928,6 @@ public class BubbleController implements ConfigurationChangeListener { return (isSummary && isSuppressedSummary) || isSuppressedBubble; } - private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) { - if (mBubbleData.isSummarySuppressed(groupKey)) { - mBubbleData.removeSuppressedSummary(groupKey); - if (callback != null) { - callback.accept(mBubbleData.getSummaryKey(groupKey)); - } - } - } - /** Promote the provided bubble from the overflow view. */ public void promoteBubbleFromOverflow(Bubble bubble) { mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); @@ -1519,14 +1513,15 @@ public class BubbleController implements ConfigurationChangeListener { /** * Description of current bubble state. */ - private void dump(PrintWriter pw, String[] args) { + private void dump(PrintWriter pw, String prefix) { pw.println("BubbleController state:"); - mBubbleData.dump(pw, args); + mBubbleData.dump(pw); pw.println(); if (mStackView != null) { - mStackView.dump(pw, args); + mStackView.dump(pw); } pw.println(); + mImpl.mCachedState.dump(pw); } /** @@ -1711,28 +1706,12 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public boolean isStackExpanded() { - return mCachedState.isStackExpanded(); - } - - @Override @Nullable public Bubble getBubbleWithShortcutId(String shortcutId) { return mCachedState.getBubbleWithShortcutId(shortcutId); } @Override - public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback, - Executor callbackExecutor) { - mMainExecutor.execute(() -> { - Consumer<String> cb = callback != null - ? (key) -> callbackExecutor.execute(() -> callback.accept(key)) - : null; - BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb); - }); - } - - @Override public void collapseStack() { mMainExecutor.execute(() -> { BubbleController.this.collapseStack(); @@ -1761,13 +1740,6 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public void openBubbleOverflow() { - mMainExecutor.execute(() -> { - BubbleController.this.openBubbleOverflow(); - }); - } - - @Override public boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback, Executor callbackExecutor) { @@ -1882,18 +1854,6 @@ public class BubbleController implements ConfigurationChangeListener { mMainExecutor.execute( () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded)); } - - @Override - public void dump(PrintWriter pw, String[] args) { - try { - mMainExecutor.executeBlocking(() -> { - BubbleController.this.dump(pw, args); - mCachedState.dump(pw); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump BubbleController in 2s"); - } - } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index fa86c8436647..c64133f0b668 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -1136,7 +1136,7 @@ public class BubbleData { /** * Description of current bubble data state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.print("selected: "); pw.println(mSelectedBubble != null ? mSelectedBubble.getKey() @@ -1147,13 +1147,13 @@ public class BubbleData { pw.print("stack bubble count: "); pw.println(mBubbles.size()); for (Bubble bubble : mBubbles) { - bubble.dump(pw, args); + bubble.dump(pw); } pw.print("overflow bubble count: "); pw.println(mOverflowBubbles.size()); for (Bubble bubble : mOverflowBubbles) { - bubble.dump(pw, args); + bubble.dump(pw); } pw.print("summaryKeys: "); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 2666a0e186b3..cfbe1b3caf7a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -1044,7 +1044,7 @@ public class BubbleExpandedView extends LinearLayout { /** * Description of current expanded view state. */ - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + public void dump(@NonNull PrintWriter pw) { pw.print("BubbleExpandedView"); pw.print(" taskId: "); pw.println(mTaskId); pw.print(" stackView: "); pw.println(mStackView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 2d0be066beb5..5bf88b119661 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -299,7 +299,7 @@ public class BubbleStackView extends FrameLayout private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker; /** Description of current animation controller state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.println("Stack view state:"); String bubblesOnScreen = BubbleDebugConfig.formatBubblesString( @@ -313,8 +313,8 @@ public class BubbleStackView extends FrameLayout pw.print(" expandedContainerMatrix: "); pw.println(mExpandedViewContainer.getAnimationMatrix()); - mStackAnimationController.dump(pw, args); - mExpandedAnimationController.dump(pw, args); + mStackAnimationController.dump(pw); + mExpandedAnimationController.dump(pw); if (mExpandedBubble != null) { pw.println("Expanded bubble state:"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 37b96ffe5cd1..0e97e9e74114 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -35,7 +35,6 @@ import androidx.annotation.Nullable; import com.android.wm.shell.common.annotations.ExternalThread; -import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.HashMap; @@ -91,18 +90,6 @@ public interface Bubbles { */ boolean isBubbleExpanded(String key); - /** @return {@code true} if stack of bubbles is expanded or not. */ - boolean isStackExpanded(); - - /** - * Removes a group key indicating that the summary for this group should no longer be - * suppressed. - * - * @param callback If removed, this callback will be called with the summary key of the group - */ - void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback, - Executor callbackExecutor); - /** Tell the stack of bubbles to collapse. */ void collapseStack(); @@ -130,9 +117,6 @@ public interface Bubbles { /** Called for any taskbar changes. */ void onTaskbarChanged(Bundle b); - /** Open the overflow view. */ - void openBubbleOverflow(); - /** * We intercept notification entries (including group summaries) dismissed by the user when * there is an active bubble associated with it. We do this so that developers can still @@ -252,9 +236,6 @@ public interface Bubbles { */ void onUserRemoved(int removedUserId); - /** Description of current bubble state. */ - void dump(PrintWriter pw, String[] args); - /** Listener to find out about stack expansion / collapse events. */ interface BubbleExpandListener { /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index b521cb6a3d38..ae434bcec6c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -468,7 +468,7 @@ public class ExpandedAnimationController } /** Description of current animation controller state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.println("ExpandedAnimationController state:"); pw.print(" isActive: "); pw.println(isActiveController()); pw.print(" animatingExpand: "); pw.println(mAnimatingExpand); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 0a1b4d70fb2b..4e2cbfd82fcc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -431,7 +431,7 @@ public class StackAnimationController extends } /** Description of current animation controller state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.println("StackAnimationController state:"); pw.print(" isActive: "); pw.println(isActiveController()); pw.print(" restingStackPos: "); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index e22c9517f4ab..8022e9b1cd81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -66,6 +66,7 @@ public abstract class TvPipModule { @Provides static Optional<Pip> providePip( Context context, + ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, @@ -84,6 +85,7 @@ public abstract class TvPipModule { return Optional.of( TvPipController.create( context, + shellInit, shellController, tvPipBoundsState, tvPipBoundsAlgorithm, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index a6a04cf67b3c..ceaa64ef8290 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -627,11 +627,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static ActivityEmbeddingController provideActivityEmbeddingController( + static Optional<ActivityEmbeddingController> provideActivityEmbeddingController( Context context, ShellInit shellInit, Transitions transitions) { - return new ActivityEmbeddingController(context, shellInit, transitions); + return Optional.ofNullable( + ActivityEmbeddingController.create(context, shellInit, transitions)); } // @@ -686,7 +687,7 @@ public abstract class WMShellBaseModule { Optional<RecentTasksController> recentTasksOptional, Optional<OneHandedController> oneHandedControllerOptional, Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional, - ActivityEmbeddingController activityEmbeddingOptional, + Optional<ActivityEmbeddingController> activityEmbeddingOptional, Transitions transitions, StartingWindowController startingWindow, @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) { 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 2bcc134f9dd5..4fe32556a94d 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 @@ -145,6 +145,7 @@ public abstract class WMShellModule { @Provides static BubbleController provideBubbleController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, FloatingContentCoordinator floatingContentCoordinator, @@ -165,7 +166,7 @@ public abstract class WMShellModule { @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { - return new BubbleController(context, shellInit, shellController, data, + return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, null /* synchronizer */, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, userManager, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index 76c0f41997ad..7129165a78dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -37,16 +37,6 @@ public interface OneHanded { } /** - * Return one handed settings enabled or not. - */ - boolean isOneHandedEnabled(); - - /** - * Return swipe to notification settings enabled or not. - */ - boolean isSwipeToNotificationEnabled(); - - /** * Enters one handed mode. */ void startOneHanded(); @@ -80,9 +70,4 @@ public interface OneHanded { * transition start or finish */ void registerTransitionCallback(OneHandedTransitionCallback callback); - - /** - * Notifies when user switch complete - */ - void onUserSwitch(int userId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 9149204b94ce..e0c4fe8c4fba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -59,6 +59,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import java.io.PrintWriter; @@ -67,7 +68,7 @@ import java.io.PrintWriter; */ public class OneHandedController implements RemoteCallable<OneHandedController>, DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener, - KeyguardChangeListener { + KeyguardChangeListener, UserChangeListener { private static final String TAG = "OneHandedController"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = @@ -76,8 +77,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; - private volatile boolean mIsOneHandedEnabled; - private volatile boolean mIsSwipeToNotificationEnabled; + private boolean mIsOneHandedEnabled; + private boolean mIsSwipeToNotificationEnabled; private boolean mIsShortcutEnabled; private boolean mTaskChangeToExit; private boolean mLockedDisabled; @@ -294,6 +295,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mState.addSListeners(mTutorialHandler); mShellController.addConfigurationChangeListener(this); mShellController.addKeyguardChangeListener(this); + mShellController.addUserChangeListener(this); } public OneHanded asOneHanded() { @@ -627,7 +629,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, stopOneHanded(); } - private void onUserSwitch(int newUserId) { + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { unregisterSettingObservers(); mUserId = newUserId; registerSettingObservers(newUserId); @@ -718,18 +721,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } @Override - public boolean isOneHandedEnabled() { - // This is volatile so return directly - return mIsOneHandedEnabled; - } - - @Override - public boolean isSwipeToNotificationEnabled() { - // This is volatile so return directly - return mIsSwipeToNotificationEnabled; - } - - @Override public void startOneHanded() { mMainExecutor.execute(() -> { OneHandedController.this.startOneHanded(); @@ -770,13 +761,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, OneHandedController.this.registerTransitionCallback(callback); }); } - - @Override - public void onUserSwitch(int userId) { - mMainExecutor.execute(() -> { - OneHandedController.this.onUserSwitch(userId); - }); - } } /** 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 93172f82edd1..c06881ae6ad7 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 @@ -51,12 +51,6 @@ public interface Pip { } /** - * Registers the session listener for the current user. - */ - default void registerSessionListenerForCurrentUser() { - } - - /** * Sets both shelf visibility and its height. * * @param visible visibility of shelf. 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 fc97f310ad4e..ac3407dd1ca1 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 @@ -92,6 +92,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -105,7 +106,8 @@ import java.util.function.Consumer; * Manages the picture-in-picture (PIP) UI and states for Phones. */ public class PipController implements PipTransitionController.PipTransitionCallback, - RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener { + RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener, + UserChangeListener { private static final String TAG = "PipController"; private Context mContext; @@ -528,7 +530,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mOneHandedController.ifPresent(controller -> { - controller.asOneHanded().registerTransitionCallback( + controller.registerTransitionCallback( new OneHandedTransitionCallback() { @Override public void onStartFinished(Rect bounds) { @@ -542,8 +544,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); }); + mMediaController.registerSessionListenerForCurrentUser(); + mShellController.addConfigurationChangeListener(this); mShellController.addKeyguardChangeListener(this); + mShellController.addUserChangeListener(this); } @Override @@ -557,6 +562,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + // Re-register the media session listener when switching users + mMediaController.registerSessionListenerForCurrentUser(); + } + + @Override public void onConfigurationChanged(Configuration newConfig) { mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); @@ -644,10 +655,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } } - private void registerSessionListenerForCurrentUser() { - mMediaController.registerSessionListenerForCurrentUser(); - } - private void onSystemUiStateChanged(boolean isValidState, int flag) { mTouchHandler.onSystemUiStateChanged(isValidState); } @@ -968,13 +975,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void registerSessionListenerForCurrentUser() { - mMainExecutor.execute(() -> { - PipController.this.registerSessionListenerForCurrentUser(); - }); - } - - @Override public void setShelfHeight(boolean visible, int height) { mMainExecutor.execute(() -> { PipController.this.setShelfHeight(visible, height); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index a24d9618032d..4e1b0469eb96 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -32,6 +32,8 @@ import android.graphics.Rect; import android.os.RemoteException; import android.view.Gravity; +import androidx.annotation.NonNull; + import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; @@ -51,6 +53,8 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,7 +68,7 @@ import java.util.Set; public class TvPipController implements PipTransitionController.PipTransitionCallback, TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate, TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener, - ConfigurationChangeListener { + ConfigurationChangeListener, UserChangeListener { private static final String TAG = "TvPipController"; static final boolean DEBUG = false; @@ -105,6 +109,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; + private final PipTransitionController mPipTransitionController; + private final TaskStackListenerImpl mTaskStackListener; + private final PipParamsChangedForwarder mPipParamsChangedForwarder; + private final DisplayController mDisplayController; + private final WindowManagerShellWrapper mWmShellWrapper; private final ShellExecutor mMainExecutor; private final TvPipImpl mImpl = new TvPipImpl(); @@ -121,6 +130,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public static Pip create( Context context, + ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, @@ -138,6 +148,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal ShellExecutor mainExecutor) { return new TvPipController( context, + shellInit, shellController, tvPipBoundsState, tvPipBoundsAlgorithm, @@ -157,6 +168,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private TvPipController( Context context, + ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, @@ -170,11 +182,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, - WindowManagerShellWrapper wmShell, + WindowManagerShellWrapper wmShellWrapper, ShellExecutor mainExecutor) { mContext = context; mMainExecutor = mainExecutor; mShellController = shellController; + mDisplayController = displayController; mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); @@ -193,16 +206,32 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mAppOpsListener = pipAppOpsListener; mPipTaskOrganizer = pipTaskOrganizer; - pipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController = pipTransitionController; + mPipParamsChangedForwarder = pipParamsChangedForwarder; + mTaskStackListener = taskStackListener; + mWmShellWrapper = wmShellWrapper; + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mPipTransitionController.registerPipTransitionCallback(this); loadConfigurations(); - registerPipParamsChangedListener(pipParamsChangedForwarder); - registerTaskStackListenerCallback(taskStackListener); - registerWmShellPinnedStackListener(wmShell); - displayController.addDisplayWindowListener(this); + registerPipParamsChangedListener(mPipParamsChangedForwarder); + registerTaskStackListenerCallback(mTaskStackListener); + registerWmShellPinnedStackListener(mWmShellWrapper); + registerSessionListenerForCurrentUser(); + mDisplayController.addDisplayWindowListener(this); mShellController.addConfigurationChangeListener(this); + mShellController.addUserChangeListener(this); + } + + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + // Re-register the media session listener when switching users + registerSessionListenerForCurrentUser(); } @Override @@ -679,11 +708,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private class TvPipImpl implements Pip { - @Override - public void registerSessionListenerForCurrentUser() { - mMainExecutor.execute(() -> { - TvPipController.this.registerSessionListenerForCurrentUser(); - }); - } + // Not used } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 4d7c8465bcc2..97e017a65b04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -97,6 +97,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { private final ImageView mArrowLeft; private final TvWindowMenuActionButton mA11yDoneButton; + private final View mPipBackground; + private final View mDimLayer; + private final ScrollView mScrollView; private final HorizontalScrollView mHorizontalScrollView; private View mFocusedButton; @@ -148,6 +151,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { mExpandButton = findViewById(R.id.tv_pip_menu_expand_button); mExpandButton.setOnClickListener(this); + mPipBackground = findViewById(R.id.tv_pip_menu_background); + mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer); + mScrollView = findViewById(R.id.tv_pip_menu_scroll); mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll); @@ -231,7 +237,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(), finishBounds.width() / (float) finishBounds.height()); if (ratioChanged) { - mPipView.animate() + mPipBackground.animate() .alpha(1f) .setInterpolator(TvPipInterpolators.EXIT) .setDuration(mResizeAnimationDuration / 2) @@ -272,7 +278,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { "%s: onPipTransitionFinished()", TAG); // Fade in content by fading out view on top. - mPipView.animate() + mPipBackground.animate() .alpha(0f) .setDuration(mResizeAnimationDuration / 2) .setInterpolator(TvPipInterpolators.ENTER) @@ -770,6 +776,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { refocusPreviousButton(); } animateAlphaTo(show ? 1 : 0, mActionButtonsContainer); + animateAlphaTo(show ? 1 : 0, mDimLayer); } private void setFrameHighlighted(boolean highlighted) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java index 1c0b35894acd..9df863163b50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java @@ -21,13 +21,13 @@ package com.android.wm.shell.sysui; */ public interface KeyguardChangeListener { /** - * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded). + * Called when the keyguard is showing (and if so, whether it is occluded). */ default void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {} /** - * Notifies the Shell when the keyguard dismiss animation has finished. + * Called when the keyguard dismiss animation has finished. * * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of * keyguard dismiss animation. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index 52ffb46bb39c..57993948886b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -25,7 +25,9 @@ import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS; +import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.pm.UserInfo; import android.content.res.Configuration; import androidx.annotation.NonNull; @@ -36,6 +38,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import java.io.PrintWriter; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -53,6 +56,9 @@ public class ShellController { new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners = + new CopyOnWriteArrayList<>(); + private Configuration mLastConfiguration; @@ -102,6 +108,22 @@ public class ShellController { mKeyguardChangeListeners.remove(listener); } + /** + * Adds a new user-change listener. The user change callbacks are not made in any + * particular order. + */ + public void addUserChangeListener(UserChangeListener listener) { + mUserChangeListeners.remove(listener); + mUserChangeListeners.add(listener); + } + + /** + * Removes an existing user-change listener. + */ + public void removeUserChangeListener(UserChangeListener listener) { + mUserChangeListeners.remove(listener); + } + @VisibleForTesting void onConfigurationChanged(Configuration newConfig) { // The initial config is send on startup and doesn't trigger listener callbacks @@ -144,6 +166,8 @@ public class ShellController { @VisibleForTesting void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard visibility changed: visible=%b " + + "occluded=%b animatingDismiss=%b", visible, occluded, animatingDismiss); for (KeyguardChangeListener listener : mKeyguardChangeListeners) { listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss); } @@ -151,17 +175,35 @@ public class ShellController { @VisibleForTesting void onKeyguardDismissAnimationFinished() { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard dismiss animation finished"); for (KeyguardChangeListener listener : mKeyguardChangeListeners) { listener.onKeyguardDismissAnimationFinished(); } } + @VisibleForTesting + void onUserChanged(int newUserId, @NonNull Context userContext) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User changed: id=%d", newUserId); + for (UserChangeListener listener : mUserChangeListeners) { + listener.onUserChanged(newUserId, userContext); + } + } + + @VisibleForTesting + void onUserProfilesChanged(@NonNull List<UserInfo> profiles) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User profiles changed"); + for (UserChangeListener listener : mUserChangeListeners) { + listener.onUserProfilesChanged(profiles); + } + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size()); pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration); pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size()); + pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size()); } /** @@ -220,5 +262,17 @@ public class ShellController { mMainExecutor.execute(() -> ShellController.this.onKeyguardDismissAnimationFinished()); } + + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + mMainExecutor.execute(() -> + ShellController.this.onUserChanged(newUserId, userContext)); + } + + @Override + public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) { + mMainExecutor.execute(() -> + ShellController.this.onUserProfilesChanged(profiles)); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java index 254c253b0042..2108c824ac6f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -16,9 +16,14 @@ package com.android.wm.shell.sysui; +import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; +import androidx.annotation.NonNull; + import java.io.PrintWriter; +import java.util.List; /** * General interface for notifying the Shell of common SysUI events like configuration or keyguard @@ -59,4 +64,14 @@ public interface ShellInterface { * Notifies the Shell when the keyguard dismiss animation has finished. */ default void onKeyguardDismissAnimationFinished() {} + + /** + * Notifies the Shell when the user changes. + */ + default void onUserChanged(int newUserId, @NonNull Context userContext) {} + + /** + * Notifies the Shell when a profile belonging to the user changes. + */ + default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {} } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java index 710e5f6eacd3..3d0909f6128d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java @@ -14,20 +14,26 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline +package com.android.wm.shell.sysui; -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import android.content.Context; +import android.content.pm.UserInfo; + +import androidx.annotation.NonNull; + +import java.util.List; /** - * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it - * receives in [emitValue]. + * Callbacks for when the user or user's profiles changes. */ -class FakeConnectivityInfoCollector : ConnectivityInfoCollector { - private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo()) - override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow() +public interface UserChangeListener { + /** + * Called when the current (parent) user changes. + */ + default void onUserChanged(int newUserId, @NonNull Context userContext) {} - suspend fun emitValue(value: RawConnectivityInfo) { - _rawConnectivityInfoFlow.emit(value) - } + /** + * Called when a profile belonging to the user changes. + */ + default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl index bdcdb63d2cd6..cc4d268a0000 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -34,4 +34,9 @@ interface IShellTransitions { * Unregisters a remote transition handler. */ oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2; + + /** + * Retrieves the apply-token used by transactions in Shell + */ + IBinder getShellApplyToken() = 3; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 26d0ec637ccf..d2e8624171f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -219,6 +219,8 @@ public class Transitions implements RemoteCallable<Transitions> { + "use ShellInit callbacks to ensure proper ordering"); } mHandlers.add(handler); + // Set initial scale settings. + handler.setAnimScaleSetting(mTransitionAnimationScaleSetting); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s", handler.getClass().getSimpleName()); } @@ -956,6 +958,11 @@ public class Transitions implements RemoteCallable<Transitions> { transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); }); } + + @Override + public IBinder getShellApplyToken() { + return SurfaceControl.Transaction.getDefaultApplyToken(); + } } private class SettingsObserver extends ContentObserver { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt index 0a54b8c016fb..a8154e818a04 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt @@ -32,7 +32,6 @@ import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible -import com.android.server.wm.traces.common.ComponentMatcher import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.Assume import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 330c9c95e484..cb74315732ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -20,6 +20,7 @@ package com.android.wm.shell.flicker import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject import com.android.server.wm.flicker.traces.layers.LayersTraceSubject import com.android.server.wm.traces.common.IComponentMatcher import com.android.server.wm.traces.common.region.Region @@ -94,25 +95,27 @@ fun FlickerTestParameter.layerKeepVisible( fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible( component: IComponentMatcher, - splitLeftTop: Boolean + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayers { - this.isInvisible(component) + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() - .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) - .isVisible(component) + .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) .then() - .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) - .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation) + .splitAppLayerBoundsSnapToDivider( + component, landscapePosLeft, portraitPosTop, endRotation) } } fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible( component: IComponentMatcher, - splitLeftTop: Boolean + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayers { - this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation) + this.splitAppLayerBoundsSnapToDivider( + component, landscapePosLeft, portraitPosTop, endRotation) .then() .isVisible(component, true) .then() @@ -122,58 +125,96 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible( fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd( component: IComponentMatcher, - splitLeftTop: Boolean + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayersEnd { - val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(component).coversAtMost( - if (splitLeftTop) { - getSplitLeftTopRegion(dividerRegion, endRotation) - } else { - getSplitRightBottomRegion(dividerRegion, endRotation) - } - ) + splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation) } } fun FlickerTestParameter.splitAppLayerBoundsKeepVisible( component: IComponentMatcher, - splitLeftTop: Boolean + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayers { - this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation) + splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation) } } fun FlickerTestParameter.splitAppLayerBoundsChanges( component: IComponentMatcher, - splitLeftTop: Boolean + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayers { - if (splitLeftTop) { - this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation) + if (landscapePosLeft) { + this.splitAppLayerBoundsSnapToDivider( + component, landscapePosLeft, portraitPosTop, endRotation) .then() .isInvisible(component) .then() - .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation) + .splitAppLayerBoundsSnapToDivider( + component, landscapePosLeft, portraitPosTop, endRotation) } else { - this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation) + this.splitAppLayerBoundsSnapToDivider( + component, landscapePosLeft, portraitPosTop, endRotation) } } } fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider( component: IComponentMatcher, - splitLeftTop: Boolean, + landscapePosLeft: Boolean, + portraitPosTop: Boolean, rotation: Int ): LayersTraceSubject { return invoke("splitAppLayerBoundsSnapToDivider") { - val dividerRegion = it.layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region - it.visibleRegion(component).coversAtMost( - if (splitLeftTop) { - getSplitLeftTopRegion(dividerRegion, rotation) + it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation) + } +} + +fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean, + rotation: Int +): LayerTraceEntrySubject { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return invoke { + val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(component).coversAtMost( + if (displayBounds.width > displayBounds.height) { + if (landscapePosLeft) { + Region.from( + 0, + 0, + (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, + displayBounds.bounds.bottom) + } else { + Region.from( + (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, + 0, + displayBounds.bounds.right, + displayBounds.bounds.bottom + ) + } } else { - getSplitRightBottomRegion(dividerRegion, rotation) + if (portraitPosTop) { + Region.from( + 0, + 0, + displayBounds.bounds.right, + (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2) + } else { + Region.from( + 0, + (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2, + displayBounds.bounds.right, + displayBounds.bounds.bottom + ) + } } ) } @@ -185,6 +226,10 @@ fun FlickerTestParameter.appWindowBecomesVisible( assertWm { this.isAppWindowInvisible(component) .then() + .notContains(component, isOptional = true) + .then() + .isAppWindowInvisible(component, isOptional = true) + .then() .isAppWindowVisible(component) } } @@ -208,7 +253,7 @@ fun FlickerTestParameter.appWindowIsVisibleAtEnd( } fun FlickerTestParameter.appWindowKeepVisible( - component: IComponentMatcher + component: IComponentMatcher ) { assertWm { this.isAppWindowVisible(component) @@ -316,39 +361,3 @@ fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region { ) } } - -fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region { - val displayBounds = WindowUtils.getDisplayBounds(rotation) - return if (displayBounds.width > displayBounds.height) { - Region.from( - 0, - 0, - (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, - displayBounds.bounds.bottom) - } else { - Region.from( - 0, - 0, - displayBounds.bounds.right, - (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2) - } -} - -fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region { - val displayBounds = WindowUtils.getDisplayBounds(rotation) - return if (displayBounds.width > displayBounds.height) { - Region.from( - (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, - 0, - displayBounds.bounds.right, - displayBounds.bounds.bottom - ) - } else { - Region.from( - 0, - (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2, - displayBounds.bounds.right, - displayBounds.bounds.bottom - ) - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index 8b717a0cb75e..06361f9ca4ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -17,10 +17,10 @@ @file:JvmName("CommonConstants") package com.android.wm.shell.flicker -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" -val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentMatcher("", "AppPairSplitDivider#") -val DOCKED_STACK_DIVIDER_COMPONENT = ComponentMatcher("", "DockedStackDivider#") -val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentMatcher("", "StageCoordinatorSplitDivider#") -val SPLIT_DECOR_MANAGER = ComponentMatcher("", "SplitDecorManager#") +val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#") +val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#") +val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#") +val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt index ffbac39c6078..826cc2ef16d6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher class AppPairsHelper( instrumentation: Instrumentation, activityLabel: String, - component: IComponentMatcher + component: ComponentNameMatcher ) : BaseAppHelper(instrumentation, activityLabel, component) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt index c4379e9a27cc..01ba9907c24c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt @@ -26,13 +26,13 @@ import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.compatibility.common.util.SystemUtil import com.android.server.wm.flicker.helpers.StandardAppHelper -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.IComponentNameMatcher import java.io.IOException abstract class BaseAppHelper( instrumentation: Instrumentation, launcherName: String, - component: IComponentMatcher + component: IComponentNameMatcher ) : StandardAppHelper( instrumentation, launcherName, @@ -46,9 +46,6 @@ abstract class BaseAppHelper( hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) } - val defaultWindowName: String - get() = toWindowName() - val ui: UiObject2? get() = uiDevice.findObject(appSelector) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt index 92b1d216f5ab..245a82f938b3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt @@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation import android.content.Context import android.provider.Settings -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher class MultiWindowHelper( instrumentation: Instrumentation, activityLabel: String, - componentsInfo: IComponentMatcher + componentsInfo: ComponentNameMatcher ) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { companion object { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index a1226e682e05..e7f9d9a9d73d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -28,6 +28,7 @@ import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.IComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER @@ -37,8 +38,8 @@ import com.android.wm.shell.flicker.testapp.Components class SplitScreenHelper( instrumentation: Instrumentation, activityLabel: String, - componentsInfo: IComponentMatcher -) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { + componentInfo: IComponentNameMatcher +) : BaseAppHelper(instrumentation, activityLabel, componentInfo) { companion object { const val TEST_REPETITIONS = 1 @@ -104,6 +105,25 @@ class SplitScreenHelper( .waitForAndVerify() } + fun splitFromOverview(tapl: LauncherInstrumentation) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + tapl.workspace.switchToOverview().overviewActions + .clickSplit() + .currentTask + .open() + } else { + tapl.workspace.switchToOverview().currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + fun dragFromNotificationToSplit( instrumentation: Instrumentation, device: UiDevice, @@ -280,12 +300,12 @@ class SplitScreenHelper( fun copyContentFromLeftToRight( instrumentation: Instrumentation, device: UiDevice, - sourceApp: IComponentMatcher, - destinationApp: IComponentMatcher, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, ) { // Copy text from sourceApp val textView = device.wait(Until.findObject( - By.res(sourceApp.packageNames.firstOrNull(), "SplitScreenTest")), TIMEOUT_MS) + By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS) longPress(instrumentation, textView.getVisibleCenter()) val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) @@ -293,7 +313,7 @@ class SplitScreenHelper( // Paste text to destinationApp val editText = device.wait(Until.findObject( - By.res(destinationApp.packageNames.firstOrNull(), "plain_text_input")), TIMEOUT_MS) + By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS) longPress(instrumentation, editText.getVisibleCenter()) val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 045022414fe0..d194472b9000 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -25,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -155,9 +155,9 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec @Test fun launcherLayerBecomesVisible() { testSpec.assertLayers { - isInvisible(ComponentMatcher.LAUNCHER) + isInvisible(ComponentNameMatcher.LAUNCHER) .then() - .isVisible(ComponentMatcher.LAUNCHER) + .isVisible(ComponentNameMatcher.LAUNCHER) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index dff447b97b9b..507562b00f4f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -30,7 +30,6 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index 33f787182868..fd1fe65fa3a8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -23,7 +23,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.ComponentMatcher.Companion.LAUNCHER +import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER import org.junit.Test /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 5b5b9fc174f6..31a39c190dd6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -24,7 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -63,9 +63,9 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti val pipCenterY = pipRegion.centerY() val displayCenterX = device.displayWidth / 2 val barComponent = if (testSpec.isTablet) { - ComponentMatcher.TASK_BAR + ComponentNameMatcher.TASK_BAR } else { - ComponentMatcher.NAV_BAR + ComponentNameMatcher.NAV_BAR } val barLayerHeight = wmHelper.currentState.layerState .getLayerWithBuffer(barComponent) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 1c0bd0caa901..fd661cfc3ee1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -25,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -151,7 +151,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition @Test fun launcherIsAlwaysVisible() { testSpec.assertLayers { - isVisible(ComponentMatcher.LAUNCHER) + isVisible(ComponentNameMatcher.LAUNCHER) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 911d402cfde7..454927e57aa2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -28,7 +28,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.wm.shell.flicker.helpers.ImeAppHelper import org.junit.Assume.assumeFalse import org.junit.Before @@ -105,7 +105,7 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS @Test open fun pipIsAboveAppWindow() { testSpec.assertWmTag(TAG_IME_VISIBLE) { - isAboveWindow(ComponentMatcher.IME, pipApp) + isAboveWindow(ComponentNameMatcher.IME, pipApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index f69107eae638..d23881475ad6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -92,12 +92,12 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS @Presubmit @Test fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible( - primaryApp, splitLeftTop = true) + primaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible( - textEditApp, splitLeftTop = false) + textEditApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index cd92db74af95..ba40c2740bb1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -98,7 +98,7 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen @Presubmit @Test fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index 127ac1e7162b..6828589656d6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -96,12 +96,12 @@ class DismissSplitScreenByGoHome( @Presubmit @Test fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 0f4d98d69c00..9ac7c230096b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -108,12 +108,12 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes @Presubmit @Test fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) /** {@inheritDoc} */ @Postsubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index 9564d975194b..8401c1a910b8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -94,12 +94,12 @@ class EnterSplitScreenByDragFromAllApps( @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index 3b59716180b6..168afda119a9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -109,12 +109,12 @@ class EnterSplitScreenByDragFromNotification( @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( - sendNotificationApp, splitLeftTop = true) + sendNotificationApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt index 3de98723e132..c1fce5f40b57 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt @@ -97,12 +97,12 @@ class EnterSplitScreenByDragFromTaskbar( @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt new file mode 100644 index 000000000000..8cb5d7c24ced --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.splitscreen + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +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.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen from Overview. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + eachRun { + tapl.workspace.switchToOverview().dismissAllTasks() + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + } + transitions { + SplitScreenHelper.splitFromOverview(tapl) + SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @Presubmit + @Test + fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + + @Presubmit + @Test + fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) + + @Presubmit + @Test + fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( + secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) + + @Presubmit + @Test + fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun entireScreenCovered() = + super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = + super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = + super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = + super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = + super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = + super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = + super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = + super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index bdfd9c7de32f..153056188d24 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -94,12 +94,12 @@ class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreen @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = true) + primaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, splitLeftTop = false) + secondaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt index da954d97aec2..20544bd2fc2f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt @@ -98,12 +98,12 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt index db89ff52178b..5a8604f2dccc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt @@ -97,12 +97,12 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt index c23cdb610671..adea66a49c46 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt @@ -99,12 +99,12 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, splitLeftTop = false) + primaryApp, landscapePosLeft = false, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, splitLeftTop = true) + secondaryApp, landscapePosLeft = true, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java new file mode 100644 index 000000000000..b2e45a6b3a5c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.window.TransitionInfo; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +/** + * Tests for {@link ActivityEmbeddingAnimationRunner}. + * + * Build/Install/Run: + * atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase { + + @Before + public void setup() { + super.setUp(); + doNothing().when(mController).onAnimationFinished(any()); + } + + @Test + public void testStartAnimation() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + info.addChange(embeddingChange); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); + + mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction); + + final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class); + verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction), + finishCallback.capture()); + verify(mStartTransaction).apply(); + verify(mAnimator).start(); + verifyNoMoreInteractions(mFinishTransaction); + verify(mController, never()).onAnimationFinished(any()); + + // Call onAnimationFinished() when the animation is finished. + finishCallback.getValue().run(); + + verify(mController).onAnimationFinished(mTransition); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java new file mode 100644 index 000000000000..84befdddabdb --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.mock; + +import android.animation.Animator; +import android.annotation.CallSuper; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** TestBase for ActivityEmbedding animation. */ +abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { + + @Mock + ShellInit mShellInit; + @Mock + Transitions mTransitions; + @Mock + IBinder mTransition; + @Mock + SurfaceControl.Transaction mStartTransaction; + @Mock + SurfaceControl.Transaction mFinishTransaction; + @Mock + Transitions.TransitionFinishCallback mFinishCallback; + @Mock + Animator mAnimator; + + ActivityEmbeddingController mController; + ActivityEmbeddingAnimationRunner mAnimRunner; + ActivityEmbeddingAnimationSpec mAnimSpec; + + @CallSuper + @Before + public void setUp() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + MockitoAnnotations.initMocks(this); + mController = ActivityEmbeddingController.create(mContext, mShellInit, mTransitions); + assertNotNull(mController); + mAnimRunner = mController.mAnimationRunner; + assertNotNull(mAnimRunner); + mAnimSpec = mAnimRunner.mAnimationSpec; + assertNotNull(mAnimSpec); + spyOn(mController); + spyOn(mAnimRunner); + spyOn(mAnimSpec); + } + + /** Creates a mock {@link TransitionInfo.Change}. */ + static TransitionInfo.Change createChange() { + return new TransitionInfo.Change(mock(WindowContainerToken.class), + mock(SurfaceControl.class)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index bfe3b5468085..cf43b0030d2a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -16,52 +16,117 @@ package com.android.wm.shell.activityembedding; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; -import static org.junit.Assume.assumeTrue; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; + +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.Mockito.times; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.content.Context; +import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; /** - * Tests for the activity embedding controller. + * Tests for {@link ActivityEmbeddingController}. * * Build/Install/Run: * atest WMShellUnitTests:ActivityEmbeddingControllerTests */ @SmallTest @RunWith(AndroidJUnit4.class) -public class ActivityEmbeddingControllerTests extends ShellTestCase { - - private @Mock Context mContext; - private @Mock ShellInit mShellInit; - private @Mock Transitions mTransitions; - private ActivityEmbeddingController mController; +public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase { @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions)); + public void setup() { + super.setUp(); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); } @Test - public void instantiate_addInitCallback() { - assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); - verify(mShellInit, times(1)).addInitCallback(any(), any()); + public void testInstantiate() { + verify(mShellInit).addInitCallback(any(), any()); + } + + @Test + public void testOnInit() { + mController.onInit(); + + verify(mTransitions).addHandler(mController); + } + + @Test + public void testSetAnimScaleSetting() { + mController.setAnimScaleSetting(1.0f); + + verify(mAnimRunner).setAnimScaleSetting(1.0f); + verify(mAnimSpec).setAnimScaleSetting(1.0f); + } + + @Test + public void testStartAnimation_containsNonActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change nonEmbeddingChange = createChange(); + info.addChange(embeddingChange); + info.addChange(nonEmbeddingChange); + + // No-op + assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); + verifyNoMoreInteractions(mStartTransaction); + verifyNoMoreInteractions(mFinishTransaction); + verifyNoMoreInteractions(mFinishCallback); + } + + @Test + public void testStartAnimation_onlyActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + info.addChange(embeddingChange); + + // No-op + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testOnAnimationFinished() { + // Should not call finish when there is no transition. + assertThrows(IllegalStateException.class, + () -> mController.onAnimationFinished(mTransition)); + + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + info.addChange(embeddingChange); + mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback); + + verify(mFinishCallback, never()).onTransitionFinished(any(), any()); + mController.onAnimationFinished(mTransition); + verify(mFinishCallback).onTransitionFinished(any(), any()); + + // Should not call finish when the finish has already been called. + assertThrows(IllegalStateException.class, + () -> mController.onAnimationFinished(mTransition)); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 90645ce4747d..cf8297eec061 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -171,6 +171,11 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @Test + public void testControllerRegistersUserChangeListener() { + verify(mMockShellController, times(1)).addUserChangeListener(any()); + } + + @Test public void testDefaultShouldNotInOneHanded() { // Assert default transition state is STATE_NONE assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE); 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 9ed8d84d665f..eb5726bebb74 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 @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; 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.times; import static org.mockito.Mockito.verify; @@ -77,9 +78,9 @@ import java.util.Set; public class PipControllerTest extends ShellTestCase { private PipController mPipController; private ShellInit mShellInit; + private ShellController mShellController; @Mock private ShellCommandHandler mMockShellCommandHandler; - @Mock private ShellController mMockShellController; @Mock private DisplayController mMockDisplayController; @Mock private PhonePipMenuController mMockPhonePipMenuController; @Mock private PipAppOpsListener mMockPipAppOpsListener; @@ -110,8 +111,10 @@ public class PipControllerTest extends ShellTestCase { return null; }).when(mMockExecutor).execute(any()); mShellInit = spy(new ShellInit(mMockExecutor)); + mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler, + mMockExecutor)); mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, - mMockShellController, mMockDisplayController, mMockPipAppOpsListener, + mShellController, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, @@ -135,12 +138,22 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_registerConfigChangeListener() { - verify(mMockShellController, times(1)).addConfigurationChangeListener(any()); + verify(mShellController, times(1)).addConfigurationChangeListener(any()); } @Test public void instantiatePipController_registerKeyguardChangeListener() { - verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); + verify(mShellController, times(1)).addKeyguardChangeListener(any()); + } + + @Test + public void instantiatePipController_registerUserChangeListener() { + verify(mShellController, times(1)).addUserChangeListener(any()); + } + + @Test + public void instantiatePipController_registerMediaListener() { + verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser(); } @Test @@ -167,7 +180,7 @@ public class PipControllerTest extends ShellTestCase { ShellInit shellInit = new ShellInit(mMockExecutor); assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, - mMockShellController, mMockDisplayController, mMockPipAppOpsListener, + mShellController, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, @@ -264,4 +277,11 @@ public class PipControllerTest extends ShellTestCase { verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of()); } + + @Test + public void onUserChangeRegisterMediaListener() { + reset(mMockPipMediaController); + mShellController.asShell().onUserChanged(100, mContext); + verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser(); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java index 39e58ffcf9c7..d6ddba9e927d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -17,11 +17,15 @@ package com.android.wm.shell.sysui; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -35,6 +39,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; @SmallTest @@ -42,22 +48,29 @@ import java.util.Locale; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class ShellControllerTest extends ShellTestCase { + private static final int TEST_USER_ID = 100; + @Mock private ShellInit mShellInit; @Mock private ShellCommandHandler mShellCommandHandler; @Mock private ShellExecutor mExecutor; + @Mock + private Context mTestUserContext; private ShellController mController; private TestConfigurationChangeListener mConfigChangeListener; private TestKeyguardChangeListener mKeyguardChangeListener; + private TestUserChangeListener mUserChangeListener; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mKeyguardChangeListener = new TestKeyguardChangeListener(); mConfigChangeListener = new TestConfigurationChangeListener(); + mUserChangeListener = new TestUserChangeListener(); mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } @@ -68,6 +81,46 @@ public class ShellControllerTest extends ShellTestCase { } @Test + public void testAddUserChangeListener_ensureCallback() { + mController.addUserChangeListener(mUserChangeListener); + + mController.onUserChanged(TEST_USER_ID, mTestUserContext); + assertTrue(mUserChangeListener.userChanged == 1); + assertTrue(mUserChangeListener.lastUserContext == mTestUserContext); + } + + @Test + public void testDoubleAddUserChangeListener_ensureSingleCallback() { + mController.addUserChangeListener(mUserChangeListener); + mController.addUserChangeListener(mUserChangeListener); + + mController.onUserChanged(TEST_USER_ID, mTestUserContext); + assertTrue(mUserChangeListener.userChanged == 1); + assertTrue(mUserChangeListener.lastUserContext == mTestUserContext); + } + + @Test + public void testAddRemoveUserChangeListener_ensureNoCallback() { + mController.addUserChangeListener(mUserChangeListener); + mController.removeUserChangeListener(mUserChangeListener); + + mController.onUserChanged(TEST_USER_ID, mTestUserContext); + assertTrue(mUserChangeListener.userChanged == 0); + assertTrue(mUserChangeListener.lastUserContext == null); + } + + @Test + public void testUserProfilesChanged() { + mController.addUserChangeListener(mUserChangeListener); + + ArrayList<UserInfo> profiles = new ArrayList<>(); + profiles.add(mock(UserInfo.class)); + profiles.add(mock(UserInfo.class)); + mController.onUserProfilesChanged(profiles); + assertTrue(mUserChangeListener.lastUserProfiles.equals(profiles)); + } + + @Test public void testAddKeyguardChangeListener_ensureCallback() { mController.addKeyguardChangeListener(mKeyguardChangeListener); @@ -332,4 +385,27 @@ public class ShellControllerTest extends ShellTestCase { dismissAnimationFinished++; } } + + private class TestUserChangeListener implements UserChangeListener { + // Counts of number of times each of the callbacks are called + public int userChanged; + public int lastUserId; + public Context lastUserContext; + public int userProfilesChanged; + public List<? extends UserInfo> lastUserProfiles; + + + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + userChanged++; + lastUserId = newUserId; + lastUserContext = userContext; + } + + @Override + public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) { + userProfilesChanged++; + lastUserProfiles = profiles; + } + } } diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index 9a4bda2ee1df..3c67edc9a428 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -125,9 +125,14 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) { } Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families, - int weight, int italic) { + int weight, int italic, const Typeface* fallback) { Typeface* result = new Typeface; - result->fFontCollection = minikin::FontCollection::create(families); + if (fallback == nullptr) { + result->fFontCollection = minikin::FontCollection::create(std::move(families)); + } else { + result->fFontCollection = + fallback->fFontCollection->createCollectionWithFamilies(std::move(families)); + } if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) { int weightFromFont; diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h index 0c3ef01ab26b..565136e53676 100644 --- a/libs/hwui/hwui/Typeface.h +++ b/libs/hwui/hwui/Typeface.h @@ -78,7 +78,8 @@ public: Typeface* src, const std::vector<minikin::FontVariation>& variations); static Typeface* createFromFamilies( - std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic); + std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic, + const Typeface* fallback); static void setDefault(const Typeface* face); diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index f5ed5689e4e8..209b35c5537c 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -109,27 +109,14 @@ static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray, jlong fallbackPtr, int weight, int italic) { ScopedLongArrayRO families(env, familyArray); - std::vector<std::shared_ptr<minikin::FontFamily>> familyVec; Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr); - if (typeface != nullptr) { - const std::shared_ptr<minikin::FontCollection>& fallbackCollection = - toTypeface(fallbackPtr)->fFontCollection; - familyVec.reserve(families.size() + fallbackCollection->getFamilyCount()); - for (size_t i = 0; i < families.size(); i++) { - FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); - familyVec.emplace_back(family->family); - } - for (size_t i = 0; i < fallbackCollection->getFamilyCount(); i++) { - familyVec.emplace_back(fallbackCollection->getFamilyAt(i)); - } - } else { - familyVec.reserve(families.size()); - for (size_t i = 0; i < families.size(); i++) { - FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); - familyVec.emplace_back(family->family); - } + std::vector<std::shared_ptr<minikin::FontFamily>> familyVec; + familyVec.reserve(families.size()); + for (size_t i = 0; i < families.size(); i++) { + FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); + familyVec.emplace_back(family->family); } - return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic)); + return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic, typeface)); } // CriticalNative diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 81bcb4e609df..c5196eeccea3 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -54,8 +54,6 @@ typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore #include <SkColorSpace.h> #include <SkRefCnt.h> -class GrVkExtensions; - namespace android { namespace uirenderer { namespace renderthread { diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index 25cc8ca0dafb..499afa039d1f 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -73,7 +73,8 @@ std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const ch TEST(TypefaceTest, resolveDefault_and_setDefaultTest) { std::unique_ptr<Typeface> regular(Typeface::createFromFamilies( - makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + nullptr /* fallback */)); EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get())); // Keep the original to restore it later. @@ -351,24 +352,24 @@ TEST(TypefaceTest, createAbsolute) { TEST(TypefaceTest, createFromFamilies_Single) { // In Java, new // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build(); - std::unique_ptr<Typeface> regular( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false)); + std::unique_ptr<Typeface> regular(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */)); EXPECT_EQ(400, regular->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant()); EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle); // In Java, new // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build(); - std::unique_ptr<Typeface> bold( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false)); + std::unique_ptr<Typeface> bold(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */)); EXPECT_EQ(700, bold->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant()); EXPECT_EQ(Typeface::kBold, bold->fAPIStyle); // In Java, new // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build(); - std::unique_ptr<Typeface> italic( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true)); + std::unique_ptr<Typeface> italic(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */)); EXPECT_EQ(400, italic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); @@ -376,8 +377,8 @@ TEST(TypefaceTest, createFromFamilies_Single) { // In Java, // new // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build(); - std::unique_ptr<Typeface> boldItalic( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true)); + std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */)); EXPECT_EQ(700, boldItalic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); @@ -385,8 +386,8 @@ TEST(TypefaceTest, createFromFamilies_Single) { // In Java, // new // Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build(); - std::unique_ptr<Typeface> over1000( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false)); + std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */)); EXPECT_EQ(1000, over1000->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant()); EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle); @@ -394,30 +395,33 @@ TEST(TypefaceTest, createFromFamilies_Single) { TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) { // In Java, new Typeface.Builder("Family-Regular.ttf").build(); - std::unique_ptr<Typeface> regular(Typeface::createFromFamilies( - makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> regular( + Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(400, regular->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant()); EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle); // In Java, new Typeface.Builder("Family-Bold.ttf").build(); - std::unique_ptr<Typeface> bold(Typeface::createFromFamilies( - makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> bold( + Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(700, bold->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant()); EXPECT_EQ(Typeface::kBold, bold->fAPIStyle); // In Java, new Typeface.Builder("Family-Italic.ttf").build(); - std::unique_ptr<Typeface> italic(Typeface::createFromFamilies( - makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> italic( + Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(400, italic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); // In Java, new Typeface.Builder("Family-BoldItalic.ttf").build(); - std::unique_ptr<Typeface> boldItalic( - Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont), - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies( + makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + nullptr /* fallback */)); EXPECT_EQ(700, boldItalic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); @@ -427,8 +431,9 @@ TEST(TypefaceTest, createFromFamilies_Family) { std::vector<std::shared_ptr<minikin::FontFamily>> families = { buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)}; - std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies( - std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> typeface( + Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(400, typeface->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant()); } @@ -436,10 +441,24 @@ TEST(TypefaceTest, createFromFamilies_Family) { TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) { std::vector<std::shared_ptr<minikin::FontFamily>> families = { buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)}; - std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies( - std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> typeface( + Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(700, typeface->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant()); } +TEST(TypefaceTest, createFromFamilies_Family_withFallback) { + std::vector<std::shared_ptr<minikin::FontFamily>> fallbackFamilies = { + buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)}; + std::unique_ptr<Typeface> fallback( + Typeface::createFromFamilies(std::move(fallbackFamilies), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); + std::unique_ptr<Typeface> regular( + Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, fallback.get())); + EXPECT_EQ(400, regular->fStyle.weight()); + EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant()); +} + } // namespace diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 8afc7d999d2e..b6f07f43cef5 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -79,8 +79,9 @@ public final class MediaRouter2Manager { final String mPackageName; private final Context mContext; - @GuardedBy("sLock") - private Client mClient; + + private final Client mClient; + private final IMediaRouterService mMediaRouterService; private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0); final Handler mHandler; @@ -120,7 +121,12 @@ public final class MediaRouter2Manager { .getSystemService(Context.MEDIA_SESSION_SERVICE); mPackageName = mContext.getPackageName(); mHandler = new Handler(context.getMainLooper()); - mHandler.post(this::getOrCreateClient); + mClient = new Client(); + try { + mMediaRouterService.registerManager(mClient, mPackageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** @@ -167,7 +173,7 @@ public final class MediaRouter2Manager { public void registerScanRequest() { if (mScanRequestCount.getAndIncrement() == 0) { try { - mMediaRouterService.startScan(getOrCreateClient()); + mMediaRouterService.startScan(mClient); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -194,7 +200,7 @@ public final class MediaRouter2Manager { }) == 0) { try { - mMediaRouterService.stopScan(getOrCreateClient()); + mMediaRouterService.stopScan(mClient); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -358,8 +364,7 @@ public final class MediaRouter2Manager { @Nullable public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) { try { - return mMediaRouterService.getSystemSessionInfoForPackage( - getOrCreateClient(), packageName); + return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -424,7 +429,7 @@ public final class MediaRouter2Manager { @NonNull public List<RoutingSessionInfo> getRemoteSessions() { try { - return mMediaRouterService.getRemoteSessions(getOrCreateClient()); + return mMediaRouterService.getRemoteSessions(mClient); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -512,8 +517,7 @@ public final class MediaRouter2Manager { try { int requestId = mNextRequestId.getAndIncrement(); - mMediaRouterService.setRouteVolumeWithManager( - getOrCreateClient(), requestId, route, volume); + mMediaRouterService.setRouteVolumeWithManager(mClient, requestId, route, volume); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -540,7 +544,7 @@ public final class MediaRouter2Manager { try { int requestId = mNextRequestId.getAndIncrement(); mMediaRouterService.setSessionVolumeWithManager( - getOrCreateClient(), requestId, sessionInfo.getId(), volume); + mClient, requestId, sessionInfo.getId(), volume); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -765,7 +769,7 @@ public final class MediaRouter2Manager { try { int requestId = mNextRequestId.getAndIncrement(); mMediaRouterService.selectRouteWithManager( - getOrCreateClient(), requestId, sessionInfo.getId(), route); + mClient, requestId, sessionInfo.getId(), route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -804,7 +808,7 @@ public final class MediaRouter2Manager { try { int requestId = mNextRequestId.getAndIncrement(); mMediaRouterService.deselectRouteWithManager( - getOrCreateClient(), requestId, sessionInfo.getId(), route); + mClient, requestId, sessionInfo.getId(), route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -825,8 +829,7 @@ public final class MediaRouter2Manager { try { int requestId = mNextRequestId.getAndIncrement(); - mMediaRouterService.releaseSessionWithManager( - getOrCreateClient(), requestId, sessionInfo.getId()); + mMediaRouterService.releaseSessionWithManager(mClient, requestId, sessionInfo.getId()); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -843,7 +846,7 @@ public final class MediaRouter2Manager { try { mMediaRouterService.transferToRouteWithManager( - getOrCreateClient(), requestId, session.getId(), route); + mClient, requestId, session.getId(), route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -860,7 +863,7 @@ public final class MediaRouter2Manager { try { mMediaRouterService.requestCreateSessionWithManager( - getOrCreateClient(), requestId, oldSession, route); + mClient, requestId, oldSession, route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -906,22 +909,6 @@ public final class MediaRouter2Manager { sessionInfo.getOwnerPackageName()); } - private Client getOrCreateClient() { - synchronized (sLock) { - if (mClient != null) { - return mClient; - } - Client client = new Client(); - try { - mMediaRouterService.registerManager(client, mPackageName); - mClient = client; - return client; - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - } - /** * Interface for receiving events about media routing changes. */ diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp index 4df745ff6a20..30d0c35bcbb0 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -245,32 +245,23 @@ ASystemFontIterator* ASystemFontIterator_open() { std::unique_ptr<ASystemFontIterator> ite(new ASystemFontIterator()); std::unordered_set<AFont, FontHasher> fonts; - minikin::SystemFonts::getFontMap( - [&fonts](const std::vector<std::shared_ptr<minikin::FontCollection>>& collections) { - for (const auto& fc : collections) { - for (uint32_t i = 0; i < fc->getFamilyCount(); ++i) { - const auto& family = fc->getFamilyAt(i); - for (uint32_t j = 0; j < family->getNumFonts(); ++j) { - const minikin::Font* font = family->getFont(j); - - std::optional<std::string> locale; - uint32_t localeId = font->getLocaleListId(); - if (localeId != minikin::kEmptyLocaleListId) { - locale.emplace(minikin::getLocaleString(localeId)); - } - std::vector<std::pair<uint32_t, float>> axes; - for (const auto& [tag, value] : font->typeface()->GetAxes()) { - axes.push_back(std::make_pair(tag, value)); - } - - fonts.insert( - {font->typeface()->GetFontPath(), std::move(locale), - font->style().weight(), - font->style().slant() == minikin::FontStyle::Slant::ITALIC, - static_cast<uint32_t>(font->typeface()->GetFontIndex()), - axes}); - } + minikin::SystemFonts::getFontSet( + [&fonts](const std::vector<std::shared_ptr<minikin::Font>>& fontSet) { + for (const auto& font : fontSet) { + std::optional<std::string> locale; + uint32_t localeId = font->getLocaleListId(); + if (localeId != minikin::kEmptyLocaleListId) { + locale.emplace(minikin::getLocaleString(localeId)); } + std::vector<std::pair<uint32_t, float>> axes; + for (const auto& [tag, value] : font->typeface()->GetAxes()) { + axes.push_back(std::make_pair(tag, value)); + } + + fonts.insert({font->typeface()->GetFontPath(), std::move(locale), + font->style().weight(), + font->style().slant() == minikin::FontStyle::Slant::ITALIC, + static_cast<uint32_t>(font->typeface()->GetFontIndex()), axes}); } }); diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml index cb757d34dfb4..9578fcf0f4cd 100644 --- a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml +++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml @@ -1,6 +1,15 @@ <component name="ProjectCodeStyleConfiguration"> <code_scheme name="Project" version="173"> <JetCodeStyleSettings> + <option name="PACKAGES_TO_USE_STAR_IMPORTS"> + <value /> + </option> + <option name="PACKAGES_IMPORT_LAYOUT"> + <value> + <package name="" alias="false" withSubpackages="true" /> + <package name="" alias="true" withSubpackages="true" /> + </value> + </option> <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> </JetCodeStyleSettings> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt index 937e59493632..36361ddfe08a 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt @@ -17,7 +17,6 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle -import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavType @@ -28,12 +27,14 @@ import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +private const val TITLE = "Sample page with arguments" private const val STRING_PARAM_NAME = "stringParam" private const val INT_PARAM_NAME = "intParam" object ArgumentPageProvider : SettingsPageProvider { - override val name = Destinations.Argument + override val name = "Argument" override val arguments = listOf( navArgument(STRING_PARAM_NAME) { type = NavType.StringType }, @@ -51,17 +52,17 @@ object ArgumentPageProvider : SettingsPageProvider { @Composable fun EntryItem(stringParam: String, intParam: Int) { Preference(object : PreferenceModel { - override val title = "Sample page with arguments" + override val title = TITLE override val summary = "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam".toState() - override val onClick = navigator("${Destinations.Argument}/$stringParam/$intParam") + override val onClick = navigator("$name/$stringParam/$intParam") }) } } @Composable fun ArgumentPage(stringParam: String, intParam: Int) { - Column { + RegularScaffold(title = TITLE) { Preference(object : PreferenceModel { override val title = "String param value" override val summary = stringParam.toState() diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt index 143c365d3a74..82005ec44c74 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt @@ -17,12 +17,8 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.api.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator @@ -30,8 +26,11 @@ import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Footer +private const val TITLE = "Sample Footer" + object FooterPageProvider : SettingsPageProvider { override val name = "Footer" @@ -43,7 +42,7 @@ object FooterPageProvider : SettingsPageProvider { @Composable fun EntryItem() { Preference(object : PreferenceModel { - override val title = "Sample Footer" + override val title = TITLE override val onClick = navigator(name) }) } @@ -51,7 +50,7 @@ object FooterPageProvider : SettingsPageProvider { @Composable private fun FooterPage() { - Column(Modifier.verticalScroll(rememberScrollState())) { + RegularScaffold(title = TITLE) { Preference(remember { object : PreferenceModel { override val title = "Some Preference" diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt index ee077f4a25e6..6b7de8d29d0f 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt @@ -31,7 +31,7 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R object HomePageProvider : SettingsPageProvider { - override val name = Destinations.Home + override val name = "Home" @Composable override fun Page(arguments: Bundle?) { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt index 64652257c20b..cbfc60395d69 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt @@ -18,14 +18,6 @@ package com.android.settingslib.spa.gallery.page import com.android.settingslib.spa.framework.api.SettingsPageRepository -object Destinations { - const val Home = "Home" - const val Preference = "Preference" - const val SwitchPreference = "SwitchPreference" - const val Argument = "Argument" - const val Slider = "Slider" -} - val galleryPageRepository = SettingsPageRepository( allPages = listOf( HomePageProvider, @@ -36,5 +28,5 @@ val galleryPageRepository = SettingsPageRepository( SettingsPagerPageProvider, FooterPageProvider, ), - startDestination = Destinations.Home, + startDestination = HomePageProvider.name, ) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt index 8a29d35ad9d9..0463e58f09f6 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt @@ -17,9 +17,6 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DisabledByDefault import androidx.compose.material.icons.outlined.TouchApp @@ -31,7 +28,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.api.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator @@ -39,11 +35,14 @@ import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.SettingsIcon import kotlinx.coroutines.delay +private const val TITLE = "Sample Preference" + object PreferencePageProvider : SettingsPageProvider { - override val name = Destinations.Preference + override val name = "Preference" @Composable override fun Page(arguments: Bundle?) { @@ -53,15 +52,15 @@ object PreferencePageProvider : SettingsPageProvider { @Composable fun EntryItem() { Preference(object : PreferenceModel { - override val title = "Sample Preference" - override val onClick = navigator(Destinations.Preference) + override val title = TITLE + override val onClick = navigator(name) }) } } @Composable private fun PreferencePage() { - Column(Modifier.verticalScroll(rememberScrollState())) { + RegularScaffold(title = TITLE) { Preference(object : PreferenceModel { override val title = "Preference" }) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt index 5351ea65da47..df48517fb1fb 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt @@ -17,11 +17,7 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.api.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator @@ -29,7 +25,10 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.scaffold.SettingsPager -import com.android.settingslib.spa.widget.ui.SettingsTitle +import com.android.settingslib.spa.widget.scaffold.SettingsScaffold +import com.android.settingslib.spa.widget.ui.PlaceholderTitle + +private const val TITLE = "Sample SettingsPager" object SettingsPagerPageProvider : SettingsPageProvider { override val name = "SettingsPager" @@ -42,7 +41,7 @@ object SettingsPagerPageProvider : SettingsPageProvider { @Composable fun EntryItem() { Preference(object : PreferenceModel { - override val title = "Sample SettingsPager" + override val title = TITLE override val onClick = navigator(name) }) } @@ -50,9 +49,9 @@ object SettingsPagerPageProvider : SettingsPageProvider { @Composable private fun SettingsPagerPage() { - SettingsPager(listOf("Personal", "Work")) { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - SettingsTitle(title = "Page $it") + SettingsScaffold(title = TITLE) { + SettingsPager(listOf("Personal", "Work")) { + PlaceholderTitle("Page $it") } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt index 9bcac1b84a0b..04046fa18df1 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt @@ -17,9 +17,6 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccessAlarm import androidx.compose.material.icons.outlined.MusicNote @@ -29,18 +26,20 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.api.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.SettingsSlider import com.android.settingslib.spa.widget.ui.SettingsSliderModel +private const val TITLE = "Sample Slider" + object SliderPageProvider : SettingsPageProvider { - override val name = Destinations.Slider + override val name = "Slider" @Composable override fun Page(arguments: Bundle?) { @@ -50,15 +49,15 @@ object SliderPageProvider : SettingsPageProvider { @Composable fun EntryItem() { Preference(object : PreferenceModel { - override val title = "Sample Slider" - override val onClick = navigator(Destinations.Slider) + override val title = TITLE + override val onClick = navigator(name) }) } } @Composable private fun SliderPage() { - Column(Modifier.verticalScroll(rememberScrollState())) { + RegularScaffold(title = TITLE) { SettingsSlider(object : SettingsSliderModel { override val title = "Slider" override val initValue = 40 diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt index b6f725821728..e9e5d356d2d4 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt @@ -17,15 +17,11 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.api.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator @@ -35,10 +31,13 @@ import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold import kotlinx.coroutines.delay +private const val TITLE = "Sample SwitchPreference" + object SwitchPreferencePageProvider : SettingsPageProvider { - override val name = Destinations.SwitchPreference + override val name = "SwitchPreference" @Composable override fun Page(arguments: Bundle?) { @@ -48,15 +47,15 @@ object SwitchPreferencePageProvider : SettingsPageProvider { @Composable fun EntryItem() { Preference(object : PreferenceModel { - override val title = "Sample SwitchPreference" - override val onClick = navigator(Destinations.SwitchPreference) + override val title = TITLE + override val onClick = navigator(name) }) } } @Composable private fun SwitchPreferencePage() { - Column(Modifier.verticalScroll(rememberScrollState())) { + RegularScaffold(title = TITLE) { SampleSwitchPreference() SampleSwitchPreferenceWithSummary() SampleSwitchPreferenceWithAsyncSummary() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LogCompositions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LogCompositions.kt new file mode 100644 index 000000000000..4eef2a8ffbbf --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LogCompositions.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.compose + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember + +const val ENABLE_LOG_COMPOSITIONS = false + +data class LogCompositionsRef(var count: Int) + +// Note the inline function below which ensures that this function is essentially +// copied at the call site to ensure that its logging only recompositions from the +// original call site. +@Suppress("NOTHING_TO_INLINE") +@Composable +inline fun LogCompositions(tag: String, msg: String) { + if (ENABLE_LOG_COMPOSITIONS) { + val ref = remember { LogCompositionsRef(0) } + SideEffect { ref.count++ } + Log.d(tag, "Compositions $msg: ${ref.count}") + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt new file mode 100644 index 000000000000..bf338574c42d --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.filter + +/** + * ************************************************************************************************* + * This file was forked from + * https://github.com/google/accompanist/blob/main/pager/src/main/java/com/google/accompanist/pager/Pager.kt + * and will be removed once it lands in AndroidX. + */ + +/** + * A horizontally scrolling layout that allows users to flip between items to the left and right. + * + * @sample com.google.accompanist.sample.pager.HorizontalPagerSample + * + * @param count the number of pages. + * @param modifier the modifier to apply to this layout. + * @param state the state object to be used to control or observe the pager's state. + * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be + * composed from the end to the start and [PagerState.currentPage] == 0 will mean + * the first item is located at the end. + * @param itemSpacing horizontal spacing to add between items. + * @param key the scroll position will be maintained based on the key, which means if you + * add/remove items before the current visible item the item with the given key will be kept as the + * first visible one. + * @param content a block which describes the content. Inside this block you can reference + * [PagerScope.currentPage] and other properties in [PagerScope]. + */ +@Composable +fun HorizontalPager( + count: Int, + modifier: Modifier = Modifier, + state: PagerState = rememberPagerState(), + reverseLayout: Boolean = false, + itemSpacing: Dp = 0.dp, + contentPadding: PaddingValues = PaddingValues(0.dp), + verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, + key: ((page: Int) -> Any)? = null, + content: @Composable PagerScope.(page: Int) -> Unit, +) { + Pager( + count = count, + state = state, + modifier = modifier, + isVertical = false, + reverseLayout = reverseLayout, + itemSpacing = itemSpacing, + verticalAlignment = verticalAlignment, + key = key, + contentPadding = contentPadding, + content = content + ) +} + +/** + * A vertically scrolling layout that allows users to flip between items to the top and bottom. + * + * @sample com.google.accompanist.sample.pager.VerticalPagerSample + * + * @param count the number of pages. + * @param modifier the modifier to apply to this layout. + * @param state the state object to be used to control or observe the pager's state. + * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be + * composed from the bottom to the top and [PagerState.currentPage] == 0 will mean + * the first item is located at the bottom. + * @param itemSpacing vertical spacing to add between items. + * @param key the scroll position will be maintained based on the key, which means if you + * add/remove items before the current visible item the item with the given key will be kept as the + * first visible one. + * @param content a block which describes the content. Inside this block you can reference + * [PagerScope.currentPage] and other properties in [PagerScope]. + */ +@Composable +fun VerticalPager( + count: Int, + modifier: Modifier = Modifier, + state: PagerState = rememberPagerState(), + reverseLayout: Boolean = false, + itemSpacing: Dp = 0.dp, + contentPadding: PaddingValues = PaddingValues(0.dp), + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + key: ((page: Int) -> Any)? = null, + content: @Composable() (PagerScope.(page: Int) -> Unit), +) { + Pager( + count = count, + state = state, + modifier = modifier, + isVertical = true, + reverseLayout = reverseLayout, + itemSpacing = itemSpacing, + horizontalAlignment = horizontalAlignment, + key = key, + contentPadding = contentPadding, + content = content + ) +} + +@Composable +internal fun Pager( + count: Int, + modifier: Modifier, + state: PagerState, + reverseLayout: Boolean, + itemSpacing: Dp, + isVertical: Boolean, + key: ((page: Int) -> Any)?, + contentPadding: PaddingValues, + verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + content: @Composable PagerScope.(page: Int) -> Unit, +) { + require(count >= 0) { "pageCount must be >= 0" } + + LaunchedEffect(count) { + state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0) + } + + // Once a fling (scroll) has finished, notify the state + LaunchedEffect(state) { + // When a 'scroll' has finished, notify the state + snapshotFlow { state.isScrollInProgress } + .filter { !it } + // initially isScrollInProgress is false as well and we want to start receiving + // the events only after the real scroll happens. + .drop(1) + .collect { state.onScrollFinished() } + } + LaunchedEffect(state) { + snapshotFlow { state.mostVisiblePageLayoutInfo?.index } + .distinctUntilChanged() + .collect { state.updateCurrentPageBasedOnLazyListState() } + } + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) { + with(density) { + // this should be exposed on LazyListLayoutInfo instead. b/200920410 + state.afterContentPadding = if (isVertical) { + if (!reverseLayout) { + contentPadding.calculateBottomPadding() + } else { + contentPadding.calculateTopPadding() + } + } else { + if (!reverseLayout) { + contentPadding.calculateEndPadding(layoutDirection) + } else { + contentPadding.calculateStartPadding(layoutDirection) + } + }.roundToPx() + } + } + + val pagerScope = remember(state) { PagerScopeImpl(state) } + + // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate + // as normal + val consumeFlingNestedScrollConnection = remember(isVertical) { + ConsumeFlingNestedScrollConnection( + consumeHorizontal = !isVertical, + consumeVertical = isVertical, + ) + } + + if (isVertical) { + LazyColumn( + state = state.lazyListState, + verticalArrangement = Arrangement.spacedBy(itemSpacing, verticalAlignment), + horizontalAlignment = horizontalAlignment, + reverseLayout = reverseLayout, + contentPadding = contentPadding, + modifier = modifier, + ) { + items( + count = count, + key = key, + ) { page -> + Box( + Modifier + // We don't any nested flings to continue in the pager, so we add a + // connection which consumes them. + // See: https://github.com/google/accompanist/issues/347 + .nestedScroll(connection = consumeFlingNestedScrollConnection) + // Constraint the content height to be <= than the height of the pager. + .fillParentMaxHeight() + .wrapContentSize() + ) { + pagerScope.content(page) + } + } + } + } else { + LazyRow( + state = state.lazyListState, + verticalAlignment = verticalAlignment, + horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment), + reverseLayout = reverseLayout, + contentPadding = contentPadding, + modifier = modifier, + ) { + items( + count = count, + key = key, + ) { page -> + Box( + Modifier + // We don't any nested flings to continue in the pager, so we add a + // connection which consumes them. + // See: https://github.com/google/accompanist/issues/347 + .nestedScroll(connection = consumeFlingNestedScrollConnection) + // Constraint the content width to be <= than the width of the pager. + .fillParentMaxWidth() + .wrapContentSize() + ) { + pagerScope.content(page) + } + } + } + } +} + +private class ConsumeFlingNestedScrollConnection( + private val consumeHorizontal: Boolean, + private val consumeVertical: Boolean, +) : NestedScrollConnection { + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset = when (source) { + // We can consume all resting fling scrolls so that they don't propagate up to the + // Pager + NestedScrollSource.Fling -> available.consume(consumeHorizontal, consumeVertical) + else -> Offset.Zero + } + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + // We can consume all post fling velocity on the main-axis + // so that it doesn't propagate up to the Pager + return available.consume(consumeHorizontal, consumeVertical) + } +} + +private fun Offset.consume( + consumeHorizontal: Boolean, + consumeVertical: Boolean, +): Offset = Offset( + x = if (consumeHorizontal) this.x else 0f, + y = if (consumeVertical) this.y else 0f, +) + +private fun Velocity.consume( + consumeHorizontal: Boolean, + consumeVertical: Boolean, +): Velocity = Velocity( + x = if (consumeHorizontal) this.x else 0f, + y = if (consumeVertical) this.y else 0f, +) + +/** + * Scope for [HorizontalPager] content. + */ +@Stable +interface PagerScope { + /** + * Returns the current selected page + */ + val currentPage: Int + + /** + * The current offset from the start of [currentPage], as a ratio of the page width. + */ + val currentPageOffset: Float +} + +private class PagerScopeImpl( + private val state: PagerState, +) : PagerScope { + override val currentPage: Int get() = state.currentPage + override val currentPageOffset: Float get() = state.currentPageOffset +} + +/** + * Calculate the offset for the given [page] from the current scroll position. This is useful + * when using the scroll position to apply effects or animations to items. + * + * The returned offset can positive or negative, depending on whether which direction the [page] is + * compared to the current scroll position. + * + * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition + */ +fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float { + return (currentPage - page) + currentPageOffset +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt new file mode 100644 index 000000000000..21ba11739af0 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.compose + +import androidx.annotation.FloatRange +import androidx.annotation.IntRange +import androidx.compose.foundation.MutatePriority +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.gestures.ScrollableState +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import kotlin.math.abs +import kotlin.math.absoluteValue +import kotlin.math.roundToInt + +/** + * ************************************************************************************************* + * This file was forked from + * https://github.com/google/accompanist/blob/main/pager/src/main/java/com/google/accompanist/pager/PagerState.kt + * and will be removed once it lands in AndroidX. + */ + +/** + * Creates a [PagerState] that is remembered across compositions. + * + * Changes to the provided values for [initialPage] will **not** result in the state being + * recreated or changed in any way if it has already + * been created. + * + * @param initialPage the initial value for [PagerState.currentPage] + */ +@Composable +fun rememberPagerState( + @IntRange(from = 0) initialPage: Int = 0, +): PagerState = rememberSaveable(saver = PagerState.Saver) { + PagerState( + currentPage = initialPage, + ) +} + +/** + * A state object that can be hoisted to control and observe scrolling for [HorizontalPager]. + * + * In most cases, this will be created via [rememberPagerState]. + * + * @param currentPage the initial value for [PagerState.currentPage] + */ +@Stable +class PagerState( + @IntRange(from = 0) currentPage: Int = 0, +) : ScrollableState { + // Should this be public? + internal val lazyListState = LazyListState(firstVisibleItemIndex = currentPage) + + private var _currentPage by mutableStateOf(currentPage) + + // finds the page which has larger visible area within the viewport not including paddings + internal val mostVisiblePageLayoutInfo: LazyListItemInfo? + get() { + val layoutInfo = lazyListState.layoutInfo + return layoutInfo.visibleItemsInfo.maxByOrNull { + val start = maxOf(it.offset, 0) + val end = minOf( + it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding) + end - start + } + } + + internal var afterContentPadding = 0 + + private val currentPageLayoutInfo: LazyListItemInfo? + get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull { + it.index == currentPage + } + + /** + * [InteractionSource] that will be used to dispatch drag events when this + * list is being dragged. If you want to know whether the fling (or animated scroll) is in + * progress, use [isScrollInProgress]. + */ + val interactionSource: InteractionSource + get() = lazyListState.interactionSource + + /** + * The number of pages to display. + */ + @get:IntRange(from = 0) + val pageCount: Int by derivedStateOf { + lazyListState.layoutInfo.totalItemsCount + } + + /** + * The index of the currently selected page. This may not be the page which is + * currently displayed on screen. + * + * To update the scroll position, use [scrollToPage] or [animateScrollToPage]. + */ + @get:IntRange(from = 0) + var currentPage: Int + get() = _currentPage + internal set(value) { + if (value != _currentPage) { + _currentPage = value + } + } + + /** + * The current offset from the start of [currentPage], as a ratio of the page width. + * + * To update the scroll position, use [scrollToPage] or [animateScrollToPage]. + */ + val currentPageOffset: Float by derivedStateOf { + currentPageLayoutInfo?.let { + // We coerce since itemSpacing can make the offset > 1f. + // We don't want to count spacing in the offset so cap it to 1f + (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f) + } ?: 0f + } + + /** + * The target page for any on-going animations. + */ + private var animationTargetPage: Int? by mutableStateOf(null) + + /** + * Animate (smooth scroll) to the given page to the middle of the viewport. + * + * Cancels the currently running scroll, if any, and suspends until the cancellation is + * complete. + * + * @param page the page to animate to. Must be >= 0. + * @param pageOffset the percentage of the page size to offset, from the start of [page]. + * Must be in the range -1f..1f. + */ + suspend fun animateScrollToPage( + @IntRange(from = 0) page: Int, + @FloatRange(from = -1.0, to = 1.0) pageOffset: Float = 0f, + ) { + requireCurrentPage(page, "page") + requireCurrentPageOffset(pageOffset, "pageOffset") + try { + animationTargetPage = page + + // pre-jump to nearby item for long jumps as an optimization + // the same trick is done in ViewPager2 + val oldPage = lazyListState.firstVisibleItemIndex + if (abs(page - oldPage) > 3) { + lazyListState.scrollToItem(if (page > oldPage) page - 3 else page + 3) + } + + if (pageOffset.absoluteValue <= 0.005f) { + // If the offset is (close to) zero, just call animateScrollToItem and we're done + lazyListState.animateScrollToItem(index = page) + } else { + // Else we need to figure out what the offset is in pixels... + lazyListState.scroll { } // this will await for the first layout. + val layoutInfo = lazyListState.layoutInfo + var target = layoutInfo.visibleItemsInfo + .firstOrNull { it.index == page } + + if (target != null) { + // If we have access to the target page layout, we can calculate the pixel + // offset from the size + lazyListState.animateScrollToItem( + index = page, + scrollOffset = (target.size * pageOffset).roundToInt() + ) + } else if (layoutInfo.visibleItemsInfo.isNotEmpty()) { + // If we don't, we use the current page size as a guide + val currentSize = layoutInfo.visibleItemsInfo.first().size + lazyListState.animateScrollToItem( + index = page, + scrollOffset = (currentSize * pageOffset).roundToInt() + ) + + // The target should be visible now + target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull { + it.index == page + } + + if (target != null && target.size != currentSize) { + // If the size we used for calculating the offset differs from the actual + // target page size, we need to scroll again. This doesn't look great, + // but there's not much else we can do. + lazyListState.animateScrollToItem( + index = page, + scrollOffset = (target.size * pageOffset).roundToInt() + ) + } + } + } + } finally { + // We need to manually call this, as the `animateScrollToItem` call above will happen + // in 1 frame, which is usually too fast for the LaunchedEffect in Pager to detect + // the change. This is especially true when running unit tests. + onScrollFinished() + } + } + + /** + * Instantly brings the item at [page] to the middle of the viewport. + * + * Cancels the currently running scroll, if any, and suspends until the cancellation is + * complete. + * + * @param page the page to snap to. Must be >= 0. + * @param pageOffset the percentage of the page size to offset, from the start of [page]. + * Must be in the range -1f..1f. + */ + suspend fun scrollToPage( + @IntRange(from = 0) page: Int, + @FloatRange(from = -1.0, to = 1.0) pageOffset: Float = 0f, + ) { + requireCurrentPage(page, "page") + requireCurrentPageOffset(pageOffset, "pageOffset") + try { + animationTargetPage = page + + // First scroll to the given page. It will now be laid out at offset 0 + lazyListState.scrollToItem(index = page) + updateCurrentPageBasedOnLazyListState() + + // If we have a start spacing, we need to offset (scroll) by that too + if (pageOffset.absoluteValue > 0.0001f) { + currentPageLayoutInfo?.let { + scroll { + scrollBy(it.size * pageOffset) + } + } + } + } finally { + // We need to manually call this, as the `scroll` call above will happen in 1 frame, + // which is usually too fast for the LaunchedEffect in Pager to detect the change. + // This is especially true when running unit tests. + onScrollFinished() + } + } + + internal fun updateCurrentPageBasedOnLazyListState() { + // Then update the current page to our layout page + mostVisiblePageLayoutInfo?.let { + currentPage = it.index + } + } + + internal fun onScrollFinished() { + // Clear the animation target page + animationTargetPage = null + } + + override suspend fun scroll( + scrollPriority: MutatePriority, + block: suspend ScrollScope.() -> Unit + ) = lazyListState.scroll(scrollPriority, block) + + override fun dispatchRawDelta(delta: Float): Float { + return lazyListState.dispatchRawDelta(delta) + } + + override val isScrollInProgress: Boolean + get() = lazyListState.isScrollInProgress + + override fun toString(): String = "PagerState(" + + "pageCount=$pageCount, " + + "currentPage=$currentPage, " + + "currentPageOffset=$currentPageOffset" + + ")" + + private fun requireCurrentPage(value: Int, name: String) { + require(value >= 0) { "$name[$value] must be >= 0" } + } + + private fun requireCurrentPageOffset(value: Float, name: String) { + require(value in -1f..1f) { "$name must be >= 0 and <= 1" } + } + + companion object { + /** + * The default [Saver] implementation for [PagerState]. + */ + val Saver: Saver<PagerState, *> = listSaver( + save = { + listOf<Any>( + it.currentPage, + ) + }, + restore = { + PagerState( + currentPage = it[0] as Int, + ) + } + ) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index e1ca69bd7940..965436847c64 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -31,4 +31,11 @@ object SettingsDimension { end = itemPaddingEnd, bottom = itemPaddingVertical, ) + val itemPaddingAround = 8.dp + + /** The size when app icon is displayed in list. */ + val appIconItemSize = 32.dp + + /** The size when app icon is displayed in App info page. */ + val appIconInfoSize = 48.dp } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Collections.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Collections.kt new file mode 100644 index 000000000000..ba253368d505 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Collections.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.util + +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +suspend inline fun <R, T> Iterable<T>.asyncMap(crossinline transform: (T) -> R): List<R> = + coroutineScope { + map { item -> + async { transform(item) } + }.awaitAll() + } + +suspend inline fun <T> Iterable<T>.asyncFilter(crossinline predicate: (T) -> Boolean): List<T> = + asyncMap { item -> item to predicate(item) } + .filter { it.second } + .map { it.first } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt new file mode 100644 index 000000000000..999d8d7dd16a --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.util + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.snapshotFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map + +inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> = + map { list -> list.asyncMap(transform) } + +@OptIn(ExperimentalCoroutinesApi::class) +inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> = + flatMapLatest { snapshotFlow { block(it).value } } + +fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> = + combine(flow.distinctUntilChangedBy {}) { value, _ -> value } + +class StateFlowBridge<T> { + private val stateFlow = MutableStateFlow<T?>(null) + val flow = stateFlow.filterNotNull() + + fun setIfAbsent(value: T) { + if (stateFlow.value == null) { + stateFlow.value = value + } + } + + @Composable + fun Sync(state: State<T>) { + LaunchedEffect(state.value) { + stateFlow.value = state.value + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt new file mode 100644 index 000000000000..0a41a1a95936 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.scaffold + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.android.settingslib.spa.framework.compose.LocalNavController + +@Composable +internal fun NavigateUp() { + val navController = LocalNavController.current + val contentDescription = stringResource( + id = androidx.appcompat.R.string.abc_action_bar_up_description, + ) + BackAction(contentDescription) { + navController.navigateUp() + } +} + +@Composable +private fun BackAction(contentDescription: String, onClick: () -> Unit) { + IconButton(onClick) { + Icon( + imageVector = Icons.Outlined.ArrowBack, + contentDescription = contentDescription, + ) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt new file mode 100644 index 000000000000..9a17b2a8cb78 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.scaffold + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.theme.SettingsTheme + +/** + * A [Scaffold] which content is scrollable and wrapped in a [Column]. + * + * For example, this is for the pages with some preferences and is scrollable when the items out of + * the screen. + */ +@Composable +fun RegularScaffold( + title: String, + actions: @Composable RowScope.() -> Unit = {}, + content: @Composable () -> Unit, +) { + SettingsScaffold(title, actions) { paddingValues -> + Column(Modifier.verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(paddingValues)) + content() + } + } +} + +@Preview +@Composable +private fun RegularScaffoldPreview() { + SettingsTheme { + RegularScaffold(title = "Display") {} + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt index 1ec2390741ed..e0e9b951065b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt @@ -20,13 +20,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.TabRow import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import com.android.settingslib.spa.framework.compose.HorizontalPager +import com.android.settingslib.spa.framework.compose.rememberPagerState import com.android.settingslib.spa.framework.theme.SettingsDimension +import kotlin.math.absoluteValue +import kotlinx.coroutines.launch @Composable fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit) { @@ -37,10 +38,11 @@ fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit } Column { - var currentPage by rememberSaveable { mutableStateOf(0) } + val coroutineScope = rememberCoroutineScope() + val pagerState = rememberPagerState() TabRow( - selectedTabIndex = currentPage, + selectedTabIndex = pagerState.currentPage, modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd), containerColor = Color.Transparent, indicator = {}, @@ -49,12 +51,19 @@ fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit titles.forEachIndexed { page, title -> SettingsTab( title = title, - selected = currentPage == page, - onClick = { currentPage = page }, + selected = pagerState.currentPage == page, + currentPageOffset = pagerState.currentPageOffset.absoluteValue, + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage(page) + } + }, ) } } - content(currentPage) + HorizontalPager(count = titles.size, state = pagerState) { page -> + content(page) + } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt new file mode 100644 index 000000000000..ee453f246623 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.scaffold + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SmallTopAppBar +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme + +/** + * A [Scaffold] which content is can be full screen when needed. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScaffold( + title: String, + actions: @Composable RowScope.() -> Unit = {}, + content: @Composable (PaddingValues) -> Unit, +) { + Scaffold( + topBar = { + SmallTopAppBar( + title = { + Text( + text = title, + modifier = Modifier.padding(SettingsDimension.itemPaddingAround), + ) + }, + navigationIcon = { NavigateUp() }, + actions = actions, + colors = settingsTopAppBarColors(), + ) + }, + content = content, + ) +} + +@Composable +internal fun settingsTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors( + containerColor = SettingsTheme.colorScheme.surfaceHeader, + scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader, +) + +@Preview +@Composable +private fun SettingsScaffoldPreview() { + SettingsTheme { + SettingsScaffold(title = "Display") {} + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt index 16d8dbc9bf74..30a4349e942f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.lerp import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsShape @@ -35,8 +36,12 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme internal fun SettingsTab( title: String, selected: Boolean, + currentPageOffset: Float, onClick: () -> Unit, ) { + // Shows a color transition during pager scroll. + // 0f -> Selected, 1f -> Not selected + val colorFraction = if (selected) (currentPageOffset * 2).coerceAtMost(1f) else 1f Tab( selected = selected, onClick = onClick, @@ -44,29 +49,33 @@ internal fun SettingsTab( .height(48.dp) .padding(horizontal = 4.dp, vertical = 6.dp) .clip(SettingsShape.CornerMedium) - .background(color = when { - selected -> SettingsTheme.colorScheme.primaryContainer - else -> SettingsTheme.colorScheme.surface - }), + .background( + color = lerp( + start = SettingsTheme.colorScheme.primaryContainer, + stop = SettingsTheme.colorScheme.surface, + fraction = colorFraction, + ), + ), ) { Text( text = title, style = MaterialTheme.typography.labelLarge, - color = when { - selected -> SettingsTheme.colorScheme.onPrimaryContainer - else -> SettingsTheme.colorScheme.secondaryText - }, + color = lerp( + start = SettingsTheme.colorScheme.onPrimaryContainer, + stop = SettingsTheme.colorScheme.secondaryText, + fraction = colorFraction, + ), ) } } @Preview @Composable -private fun SettingsTabPreview() { +fun SettingsTabPreview() { SettingsTheme { Column { - SettingsTab(title = "Personal", selected = true) {} - SettingsTab(title = "Work", selected = false) {} + SettingsTab(title = "Personal", selected = true, currentPageOffset = 0f) {} + SettingsTab(title = "Work", selected = false, currentPageOffset = 0f) {} } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index a414c89dc24c..59b413cef56e 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -16,10 +16,14 @@ package com.android.settingslib.spa.widget.ui +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier @Composable fun SettingsTitle(title: State<String>) { @@ -50,3 +54,17 @@ fun SettingsBody(body: String) { ) } } + +@Composable +fun PlaceholderTitle(title: String) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Text( + text = title, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleLarge, + ) + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt index f608e1003163..0c84eac45cb7 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt @@ -18,6 +18,7 @@ package com.android.settingslib.spa.widget.scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertIsNotSelected import androidx.compose.ui.test.assertIsSelected import androidx.compose.ui.test.junit4.createComposeRule @@ -43,7 +44,7 @@ class SettingsPagerKtTest { composeTestRule.onNodeWithText("Personal").assertIsSelected() composeTestRule.onNodeWithText("Page 0").assertIsDisplayed() composeTestRule.onNodeWithText("Work").assertIsNotSelected() - composeTestRule.onNodeWithText("Page 1").assertDoesNotExist() + composeTestRule.onNodeWithText("Page 1").assertIsNotDisplayed() } @Test @@ -55,7 +56,7 @@ class SettingsPagerKtTest { composeTestRule.onNodeWithText("Work").performClick() composeTestRule.onNodeWithText("Personal").assertIsNotSelected() - composeTestRule.onNodeWithText("Page 0").assertDoesNotExist() + composeTestRule.onNodeWithText("Page 0").assertIsNotDisplayed() composeTestRule.onNodeWithText("Work").assertIsSelected() composeTestRule.onNodeWithText("Page 1").assertIsDisplayed() } diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp index ecbb219c64b5..a6469b5172bb 100644 --- a/packages/SettingsLib/SpaPrivileged/Android.bp +++ b/packages/SettingsLib/SpaPrivileged/Android.bp @@ -28,5 +28,8 @@ android_library { "SettingsLib", "androidx.compose.runtime_runtime", ], - kotlincflags: ["-Xjvm-default=all"], + kotlincflags: [ + "-Xjvm-default=all", + "-Xopt-in=kotlin.RequiresOptIn", + ], } diff --git a/packages/SettingsLib/SpaPrivileged/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml new file mode 100644 index 000000000000..8f8dd2b01ecd --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml @@ -0,0 +1,20 @@ +<?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> + <!-- [CHAR LIMIT=25] Text shown when there are no applications to display. --> + <string name="no_applications">No apps.</string> +</resources> diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt new file mode 100644 index 000000000000..2fa869c529d4 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt @@ -0,0 +1,26 @@ +package com.android.settingslib.spaprivileged.model.app + +import android.content.pm.ApplicationInfo +import android.icu.text.CollationKey +import kotlinx.coroutines.flow.Flow + +data class AppEntry<T : AppRecord>( + val record: T, + val label: String, + val labelCollationKey: CollationKey, +) + +interface AppListModel<T : AppRecord> { + fun getSpinnerOptions(): List<String> = emptyList() + fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>): Flow<List<T>> + fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> + + suspend fun onFirstLoaded(recordList: List<T>) {} + fun getComparator(option: Int): Comparator<AppEntry<T>> = compareBy( + { it.labelCollationKey }, + { it.record.app.packageName }, + { it.record.app.uid }, + ) + + fun getSummary(option: Int, record: T): Flow<String>? +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt new file mode 100644 index 000000000000..9265158b3b4a --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.app + +import android.app.Application +import android.content.pm.ApplicationInfo +import android.content.pm.UserInfo +import android.icu.text.Collator +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.android.settingslib.spa.framework.util.StateFlowBridge +import com.android.settingslib.spa.framework.util.asyncMapItem +import com.android.settingslib.spa.framework.util.waitFirst +import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.plus + +internal data class AppListData<T : AppRecord>( + val appEntries: List<AppEntry<T>>, + val option: Int, +) { + fun filter(predicate: (AppEntry<T>) -> Boolean) = + AppListData(appEntries.filter(predicate), option) +} + +@OptIn(ExperimentalCoroutinesApi::class) +internal class AppListViewModel<T : AppRecord>( + application: Application, +) : AndroidViewModel(application) { + val userInfo = StateFlowBridge<UserInfo>() + val listModel = StateFlowBridge<AppListModel<T>>() + val showSystem = StateFlowBridge<Boolean>() + val option = StateFlowBridge<Int>() + val searchQuery = StateFlowBridge<String>() + + private val appsRepository = AppsRepository(application) + private val appRepository = AppRepositoryImpl(application) + private val collator = Collator.getInstance().freeze() + private val labelMap = ConcurrentHashMap<String, String>() + private val scope = viewModelScope + Dispatchers.Default + + private val userIdFlow = userInfo.flow.map { it.id } + + private val recordListFlow = listModel.flow + .flatMapLatest { it.transform(userIdFlow, appsRepository.loadApps(userInfo.flow)) } + .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1) + + private val systemFilteredFlow = appsRepository.showSystemPredicate(userIdFlow, showSystem.flow) + .combine(recordListFlow) { showAppPredicate, recordList -> + recordList.filter { showAppPredicate(it.app) } + } + + val appListDataFlow = option.flow.flatMapLatest(::filterAndSort) + .combine(searchQuery.flow) { appListData, searchQuery -> + appListData.filter { + it.label.contains(other = searchQuery, ignoreCase = true) + } + } + .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1) + + init { + scheduleOnFirstLoaded() + } + + private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel -> + listModel.filter(userIdFlow, option, systemFilteredFlow) + .asyncMapItem { record -> + val label = getLabel(record.app) + AppEntry( + record = record, + label = label, + labelCollationKey = collator.getCollationKey(label), + ) + } + .map { appEntries -> + AppListData( + appEntries = appEntries.sortedWith(listModel.getComparator(option)), + option = option, + ) + } + } + + private fun scheduleOnFirstLoaded() { + recordListFlow + .waitFirst(appListDataFlow) + .combine(listModel.flow) { recordList, listModel -> + listModel.maybePreFetchLabels(recordList) + listModel.onFirstLoaded(recordList) + } + .launchIn(scope) + } + + private fun AppListModel<T>.maybePreFetchLabels(recordList: List<T>) { + if (getSpinnerOptions().isNotEmpty()) { + for (record in recordList) { + getLabel(record.app) + } + } + } + + private fun getLabel(app: ApplicationInfo) = labelMap.computeIfAbsent(app.packageName) { + appRepository.loadLabel(app) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt index 3808f64386ef..c2d85a54bc32 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.framework.app +package com.android.settingslib.spaprivileged.model.app import android.app.AppOpsManager import android.app.AppOpsManager.MODE_ALLOWED diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt index 8dde897cb23d..297868871389 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.framework.app +package com.android.settingslib.spaprivileged.model.app import android.content.pm.ApplicationInfo diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt index a6378ef53437..34f12af28dce 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.framework.app +package com.android.settingslib.spaprivileged.model.app import android.content.Context import android.content.pm.ApplicationInfo @@ -31,6 +31,8 @@ import kotlinx.coroutines.withContext fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl) interface AppRepository { + fun loadLabel(app: ApplicationInfo): String + @Composable fun produceLabel(app: ApplicationInfo): State<String> @@ -38,9 +40,11 @@ interface AppRepository { fun produceIcon(app: ApplicationInfo): State<Drawable?> } -private class AppRepositoryImpl(private val context: Context) : AppRepository { +internal class AppRepositoryImpl(private val context: Context) : AppRepository { private val packageManager = context.packageManager + override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString() + @Composable override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) { withContext(Dispatchers.Default) { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt index f6755459c1b7..6e1afd94fe29 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.framework.app +package com.android.settingslib.spaprivileged.model.app import android.content.pm.ApplicationInfo import android.os.UserHandle diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt new file mode 100644 index 000000000000..6a6462098230 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.app + +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.content.pm.UserInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +class AppsRepository(context: Context) { + private val packageManager = context.packageManager + + fun loadApps(userInfoFlow: Flow<UserInfo>): Flow<List<ApplicationInfo>> = userInfoFlow + .map { loadApps(it) } + .flowOn(Dispatchers.Default) + + private suspend fun loadApps(userInfo: UserInfo): List<ApplicationInfo> { + return coroutineScope { + val hiddenSystemModulesDeferred = async { + packageManager.getInstalledModules(0) + .filter { it.isHidden } + .map { it.packageName } + .toSet() + } + val flags = PackageManager.ApplicationInfoFlags.of( + ((if (userInfo.isAdmin) PackageManager.MATCH_ANY_USER else 0) or + PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() + ) + val installedApplicationsAsUser = + packageManager.getInstalledApplicationsAsUser(flags, userInfo.id) + + val hiddenSystemModules = hiddenSystemModulesDeferred.await() + installedApplicationsAsUser.filter { app -> + app.isInAppList(hiddenSystemModules) + } + } + } + + fun showSystemPredicate( + userIdFlow: Flow<Int>, + showSystemFlow: Flow<Boolean>, + ): Flow<(app: ApplicationInfo) -> Boolean> = + userIdFlow.combine(showSystemFlow) { userId, showSystem -> + showSystemPredicate(userId, showSystem) + } + + private suspend fun showSystemPredicate( + userId: Int, + showSystem: Boolean, + ): (app: ApplicationInfo) -> Boolean { + if (showSystem) return { true } + val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) + return { app -> + app.isUpdatedSystemApp || !app.isSystemApp || app.packageName in homeOrLauncherPackages + } + } + + private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { + val launchIntent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER) + // If we do not specify MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE, system will + // derive and update the flags according to the user's lock state. When the user is locked, + // components with ComponentInfo#directBootAware == false will be filtered. We should + // explicitly include both direct boot aware and unaware component here. + val flags = PackageManager.ResolveInfoFlags.of( + (PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DIRECT_BOOT_AWARE or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE).toLong() + ) + return coroutineScope { + val launcherActivities = async { + packageManager.queryIntentActivitiesAsUser(launchIntent, flags, userId) + } + val homeActivities = ArrayList<ResolveInfo>() + packageManager.getHomeActivities(homeActivities) + (launcherActivities.await() + homeActivities) + .map { it.activityInfo.packageName } + .toSet() + } + } + + companion object { + private fun ApplicationInfo.isInAppList(hiddenSystemModules: Set<String>) = + when { + packageName in hiddenSystemModules -> false + enabled -> true + enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true + else -> false + } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt index 66b05da03b3f..0cc497a3e899 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.framework.app +package com.android.settingslib.spaprivileged.model.app import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt index ae0cb772533c..fab3ae8e510b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.framework.enterprise +package com.android.settingslib.spaprivileged.model.enterprise import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index 5ae514cfb524..58d0f8d398f2 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -29,12 +29,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.compose.rememberDrawablePainter +import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsTitle -import com.android.settingslib.spaprivileged.framework.app.PackageManagers -import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository +import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.model.app.rememberAppRepository @Composable fun AppInfo(packageName: String, userId: Int) { @@ -45,7 +47,7 @@ fun AppInfo(packageName: String, userId: Int) { horizontalAlignment = Alignment.CenterHorizontally) { val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } Box(modifier = Modifier.padding(8.dp)) { - AppIcon(app = packageInfo.applicationInfo, size = 48) + AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize) } AppLabel(packageInfo.applicationInfo) Spacer(modifier = Modifier.height(4.dp)) @@ -54,12 +56,12 @@ fun AppInfo(packageName: String, userId: Int) { } @Composable -fun AppIcon(app: ApplicationInfo, size: Int) { +fun AppIcon(app: ApplicationInfo, size: Dp) { val appRepository = rememberAppRepository() Image( painter = rememberDrawablePainter(appRepository.produceIcon(app).value), contentDescription = null, - modifier = Modifier.size(size.dp) + modifier = Modifier.size(size) ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt new file mode 100644 index 000000000000..c60976ddea4d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.template.app + +import android.content.pm.UserInfo +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.viewmodel.compose.viewModel +import com.android.settingslib.spa.framework.compose.LogCompositions +import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.ui.PlaceholderTitle +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.model.app.AppListData +import com.android.settingslib.spaprivileged.model.app.AppListModel +import com.android.settingslib.spaprivileged.model.app.AppListViewModel +import com.android.settingslib.spaprivileged.model.app.AppRecord +import kotlinx.coroutines.Dispatchers + +private const val TAG = "AppList" + +@Composable +fun <T : AppRecord> AppList( + userInfo: UserInfo, + listModel: AppListModel<T>, + showSystem: State<Boolean>, + option: State<Int>, + searchQuery: State<String>, + appItem: @Composable (itemState: AppListItemModel<T>) -> Unit, +) { + LogCompositions(TAG, userInfo.id.toString()) + val appListData = loadAppEntries(userInfo, listModel, showSystem, option, searchQuery) + AppListWidget(appListData, listModel, appItem) +} + +@Composable +private fun <T : AppRecord> AppListWidget( + appListData: State<AppListData<T>?>, + listModel: AppListModel<T>, + appItem: @Composable (itemState: AppListItemModel<T>) -> Unit, +) { + appListData.value?.let { (list, option) -> + if (list.isEmpty()) { + PlaceholderTitle(stringResource(R.string.no_applications)) + return + } + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = rememberLazyListState(), + contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical), + ) { + items(count = list.size, key = { option to list[it].record.app.packageName }) { + val appEntry = list[it] + val summary = getSummary(listModel, option, appEntry.record) + val itemModel = remember(appEntry) { + AppListItemModel(appEntry.record, appEntry.label, summary) + } + appItem(itemModel) + } + } + } +} + +@Composable +private fun <T : AppRecord> loadAppEntries( + userInfo: UserInfo, + listModel: AppListModel<T>, + showSystem: State<Boolean>, + option: State<Int>, + searchQuery: State<String>, +): State<AppListData<T>?> { + val viewModel: AppListViewModel<T> = viewModel(key = userInfo.id.toString()) + viewModel.userInfo.setIfAbsent(userInfo) + viewModel.listModel.setIfAbsent(listModel) + viewModel.showSystem.Sync(showSystem) + viewModel.option.Sync(option) + viewModel.searchQuery.Sync(searchQuery) + + return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default) +} + +@Composable +private fun <T : AppRecord> getSummary( + listModel: AppListModel<T>, + option: Int, + record: T, +): State<String> = remember(option) { listModel.getSummary(option, record) } + ?.collectAsState(stringResource(R.string.summary_placeholder), Dispatchers.Default) + ?: "".toState() diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt new file mode 100644 index 000000000000..ac3f8ff79091 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.template.app + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.app.AppRecord + +class AppListItemModel<T : AppRecord>( + val record: T, + val label: String, + val summary: State<String>, +) + +@Composable +fun <T : AppRecord> AppListItem( + itemModel: AppListItemModel<T>, + onClick: () -> Unit, +) { + Preference(remember { + object : PreferenceModel { + override val title = itemModel.label + override val summary = itemModel.summary + override val icon = @Composable { + AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize) + } + override val onClick = onClick + } + }) +} + +@Preview +@Composable +private fun AppListItemPreview() { + SettingsTheme { + val record = object : AppRecord { + override val app = LocalContext.current.applicationInfo + } + val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()) + AppListItem(itemModel) {} + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 57e9e9ac1b9b..430ac5409711 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -31,8 +31,8 @@ import com.android.settingslib.spa.framework.api.SettingsPageProvider import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spaprivileged.framework.app.AppRecord -import com.android.settingslib.spaprivileged.framework.app.PackageManagers +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.PackageManagers import kotlinx.coroutines.Dispatchers private const val PERMISSION = "permission" diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt index 88ad9daa85b3..3782f7cd301e 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt @@ -20,7 +20,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import com.android.settingslib.spaprivileged.framework.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.AppRecord interface TogglePermissionAppListModel<T : AppRecord> { val pageTitleResId: Int diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt index 09864a17adfc..aa5ccf146d47 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.template.scaffold +package com.android.settingslib.spaprivileged.template.common import android.content.pm.UserInfo import android.os.UserHandle @@ -23,7 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.android.settingslib.spa.widget.scaffold.SettingsPager -import com.android.settingslib.spaprivileged.framework.enterprise.EnterpriseRepository +import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository @Composable fun WorkProfilePager(content: @Composable (userInfo: UserInfo) -> Unit) { diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index aab0d3a08154..06d7bb49c809 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1601,21 +1601,6 @@ <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_no_calling">No calling.</string> - <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] --> - <string name="dream_complication_title_time">Time</string> - <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] --> - <string name="dream_complication_title_date">Date</string> - <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] --> - <string name="dream_complication_title_weather">Weather</string> - <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] --> - <string name="dream_complication_title_aqi">Air Quality</string> - <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] --> - <string name="dream_complication_title_cast_info">Cast Info</string> - <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] --> - <string name="dream_complication_title_home_controls">Home Controls</string> - <!-- Screensaver overlay which displays smartspace. [CHAR LIMIT=20] --> - <string name="dream_complication_title_smartspace">Smartspace</string> - <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] --> <string name="avatar_picker_title">Choose a profile picture</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index b9c4030d9d0e..a822e185479a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -600,6 +600,9 @@ public class Utils { * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the * input NetworkCapabilities is not for a VCN network with underlying WiFi network. * + * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated + * off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}. + * * @param networkCapabilities NetworkCapabilities of the network. */ @Nullable diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 22586171e5cf..1606540da3fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -17,7 +17,6 @@ package com.android.settingslib.dream; import android.annotation.IntDef; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -32,17 +31,14 @@ import android.os.ServiceManager; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; -import android.text.TextUtils; import android.util.Log; -import com.android.settingslib.R; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -64,18 +60,21 @@ public class DreamBackend { public String toString() { StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName()); sb.append('[').append(caption); - if (isActive) + if (isActive) { sb.append(",active"); + } sb.append(',').append(componentName); - if (settingsComponentName != null) + if (settingsComponentName != null) { sb.append("settings=").append(settingsComponentName); + } return sb.append(']').toString(); } } @Retention(RetentionPolicy.SOURCE) @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER}) - public @interface WhenToDream {} + public @interface WhenToDream { + } public static final int WHILE_CHARGING = 0; public static final int WHILE_DOCKED = 1; @@ -96,7 +95,8 @@ public class DreamBackend { COMPLICATION_TYPE_SMARTSPACE }) @Retention(RetentionPolicy.SOURCE) - public @interface ComplicationType {} + public @interface ComplicationType { + } public static final int COMPLICATION_TYPE_TIME = 1; public static final int COMPLICATION_TYPE_DATE = 2; @@ -114,8 +114,6 @@ public class DreamBackend { private final boolean mDreamsActivatedOnDockByDefault; private final Set<ComponentName> mDisabledDreams; private final Set<Integer> mSupportedComplications; - private final Set<Integer> mDefaultEnabledComplications; - private static DreamBackend sInstance; public static DreamBackend getInstance(Context context) { @@ -147,13 +145,6 @@ public class DreamBackend { com.android.internal.R.array.config_supportedDreamComplications)) .boxed() .collect(Collectors.toSet()); - - mDefaultEnabledComplications = Arrays.stream(resources.getIntArray( - com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)) - .boxed() - // A complication can only be enabled by default if it is also supported. - .filter(mSupportedComplications::contains) - .collect(Collectors.toSet()); } public List<DreamInfo> getDreamInfos() { @@ -251,11 +242,12 @@ public class DreamBackend { return null; } - public @WhenToDream int getWhenToDreamSetting() { + @WhenToDream + public int getWhenToDreamSetting() { return isActivatedOnDock() && isActivatedOnSleep() ? EITHER : isActivatedOnDock() ? WHILE_DOCKED - : isActivatedOnSleep() ? WHILE_CHARGING - : NEVER; + : isActivatedOnSleep() ? WHILE_CHARGING + : NEVER; } public void setWhenToDream(@WhenToDream int whenToDream) { @@ -283,98 +275,29 @@ public class DreamBackend { } } - /** Returns whether a particular complication is enabled */ - public boolean isComplicationEnabled(@ComplicationType int complication) { - return getEnabledComplications().contains(complication); - } - /** Gets all complications which have been enabled by the user. */ public Set<Integer> getEnabledComplications() { - final String enabledComplications = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS); - - if (enabledComplications == null) { - return mDefaultEnabledComplications; - } - - return parseFromString(enabledComplications); + return getComplicationsEnabled() ? mSupportedComplications : Collections.emptySet(); } - /** Gets all dream complications which are supported on this device. **/ - public Set<Integer> getSupportedComplications() { - return mSupportedComplications; - } - - /** - * Enables or disables a particular dream complication. - * - * @param complicationType The dream complication to be enabled/disabled. - * @param value If true, the complication is enabled. Otherwise it is disabled. - */ - public void setComplicationEnabled(@ComplicationType int complicationType, boolean value) { - if (!mSupportedComplications.contains(complicationType)) return; - - Set<Integer> enabledComplications = getEnabledComplications(); - if (value) { - enabledComplications.add(complicationType); - } else { - enabledComplications.remove(complicationType); - } - - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, - convertToString(enabledComplications)); + /** Sets complication enabled state. */ + public void setComplicationsEnabled(boolean enabled) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0); } /** - * Gets the title of a particular complication type to be displayed to the user. If there - * is no title, null is returned. + * Gets whether complications are enabled on this device */ - @Nullable - public CharSequence getComplicationTitle(@ComplicationType int complicationType) { - int res = 0; - switch (complicationType) { - case COMPLICATION_TYPE_TIME: - res = R.string.dream_complication_title_time; - break; - case COMPLICATION_TYPE_DATE: - res = R.string.dream_complication_title_date; - break; - case COMPLICATION_TYPE_WEATHER: - res = R.string.dream_complication_title_weather; - break; - case COMPLICATION_TYPE_AIR_QUALITY: - res = R.string.dream_complication_title_aqi; - break; - case COMPLICATION_TYPE_CAST_INFO: - res = R.string.dream_complication_title_cast_info; - break; - case COMPLICATION_TYPE_HOME_CONTROLS: - res = R.string.dream_complication_title_home_controls; - break; - case COMPLICATION_TYPE_SMARTSPACE: - res = R.string.dream_complication_title_smartspace; - break; - default: - return null; - } - return mContext.getString(res); - } - - private static String convertToString(Set<Integer> set) { - return set.stream() - .map(String::valueOf) - .collect(Collectors.joining(",")); + public boolean getComplicationsEnabled() { + return Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, 1) == 1; } - private static Set<Integer> parseFromString(String string) { - if (TextUtils.isEmpty(string)) { - return new HashSet<>(); - } - return Arrays.stream(string.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + /** Gets all dream complications which are supported on this device. **/ + public Set<Integer> getSupportedComplications() { + return mSupportedComplications; } public boolean isEnabled() { @@ -416,10 +339,11 @@ public class DreamBackend { public void setActiveDream(ComponentName dream) { logd("setActiveDream(%s)", dream); - if (mDreamManager == null) + if (mDreamManager == null) { return; + } try { - ComponentName[] dreams = { dream }; + ComponentName[] dreams = {dream}; mDreamManager.setDreamComponents(dream == null ? null : dreams); } catch (RemoteException e) { Log.w(TAG, "Failed to set active dream to " + dream, e); @@ -427,8 +351,9 @@ public class DreamBackend { } public ComponentName getActiveDream() { - if (mDreamManager == null) + if (mDreamManager == null) { return null; + } try { ComponentName[] dreams = mDreamManager.getDreamComponents(); return dreams != null && dreams.length > 0 ? dreams[0] : null; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS new file mode 100644 index 000000000000..d40f322042fe --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS @@ -0,0 +1,2 @@ +# Default reviewers for this and subdirectories. +shaoweishen@google.com diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java index 86f7850cf1f2..52b9227fb373 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; @@ -34,29 +35,35 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowSettings; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowSettings.ShadowSecure.class}) public final class DreamBackendTest { private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3}; - private static final int[] DEFAULT_DREAM_COMPLICATIONS = {1, 3, 4}; + private static final List<Integer> SUPPORTED_DREAM_COMPLICATIONS_LIST = Arrays.stream( + SUPPORTED_DREAM_COMPLICATIONS).boxed().collect( + Collectors.toList()); @Mock private Context mContext; + @Mock + private ContentResolver mMockResolver; private DreamBackend mBackend; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getApplicationContext()).thenReturn(mContext); + when(mContext.getContentResolver()).thenReturn(mMockResolver); final Resources res = mock(Resources.class); when(mContext.getResources()).thenReturn(res); when(res.getIntArray( com.android.internal.R.array.config_supportedDreamComplications)).thenReturn( SUPPORTED_DREAM_COMPLICATIONS); - when(res.getIntArray( - com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)).thenReturn( - DEFAULT_DREAM_COMPLICATIONS); when(res.getStringArray( com.android.internal.R.array.config_disabledDreamComponents)).thenReturn( new String[]{}); @@ -69,31 +76,25 @@ public final class DreamBackendTest { } @Test - public void testSupportedComplications() { - assertThat(mBackend.getSupportedComplications()).containsExactly(1, 2, 3); - } - - @Test - public void testGetEnabledDreamComplications_default() { - assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); - } - - @Test - public void testEnableComplication() { - mBackend.setComplicationEnabled(/* complicationType= */ 2, true); - assertThat(mBackend.getEnabledComplications()).containsExactly(1, 2, 3); + public void testComplicationsEnabledByDefault() { + assertThat(mBackend.getComplicationsEnabled()).isTrue(); + assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( + SUPPORTED_DREAM_COMPLICATIONS_LIST); } @Test - public void testEnableComplication_notSupported() { - mBackend.setComplicationEnabled(/* complicationType= */ 5, true); - assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); + public void testEnableComplicationExplicitly() { + mBackend.setComplicationsEnabled(true); + assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( + SUPPORTED_DREAM_COMPLICATIONS_LIST); + assertThat(mBackend.getComplicationsEnabled()).isTrue(); } @Test - public void testDisableComplication() { - mBackend.setComplicationEnabled(/* complicationType= */ 1, false); - assertThat(mBackend.getEnabledComplications()).containsExactly(3); + public void testDisableComplications() { + mBackend.setComplicationsEnabled(false); + assertThat(mBackend.getEnabledComplications()).isEmpty(); + assertThat(mBackend.getComplicationsEnabled()).isFalse(); } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 652e281e67e9..78dea891bc12 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -85,6 +85,7 @@ <uses-permission android:name="android.permission.CONTROL_VPN" /> <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/> <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/> + <uses-permission android:name="android.permission.NETWORK_STACK"/> <!-- Physical hardware --> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml index 4e92884f39f3..a4e7a5f12db4 100644 --- a/packages/SystemUI/res-keyguard/values-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml @@ -22,7 +22,6 @@ <dimen name="keyguard_eca_top_margin">0dp</dimen> <dimen name="keyguard_eca_bottom_margin">2dp</dimen> <dimen name="keyguard_password_height">26dp</dimen> - <dimen name="num_pad_entry_row_margin_bottom">0dp</dimen> <!-- The size of PIN text in the PIN unlock method. --> <integer name="scaled_password_text_size">26</integer> diff --git a/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml index f465be4f5228..0421135b31a5 100644 --- a/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml @@ -22,7 +22,6 @@ <dimen name="keyguard_eca_top_margin">4dp</dimen> <dimen name="keyguard_eca_bottom_margin">4dp</dimen> <dimen name="keyguard_password_height">50dp</dimen> - <dimen name="num_pad_entry_row_margin_bottom">4dp</dimen> <!-- The size of PIN text in the PIN unlock method. --> <integer name="scaled_password_text_size">40</integer> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index acf3e4dcf02a..32871f0abb4f 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -86,7 +86,7 @@ <!-- Spacing around each button used for PIN view --> <dimen name="num_pad_key_width">72dp</dimen> - <dimen name="num_pad_entry_row_margin_bottom">16dp</dimen> + <dimen name="num_pad_entry_row_margin_bottom">12dp</dimen> <dimen name="num_pad_row_margin_bottom">6dp</dimen> <dimen name="num_pad_key_margin_end">12dp</dimen> diff --git a/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml b/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml new file mode 100644 index 000000000000..753ba2f21faf --- /dev/null +++ b/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2022, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/wifi_combo" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" > + + <include layout="@layout/status_bar_wifi_group_inner" /> + +</com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView> diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml index 35cce25d45a7..6cb6993bb762 100644 --- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml +++ b/packages/SystemUI/res/layout/status_bar_wifi_group.xml @@ -18,70 +18,11 @@ --> <com.android.systemui.statusbar.StatusBarWifiView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/wifi_combo" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" > - <com.android.keyguard.AlphaOptimizedLinearLayout - android:id="@+id/wifi_group" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:layout_marginStart="2.5dp" - > - <FrameLayout - android:id="@+id/inout_container" - android:layout_height="17dp" - android:layout_width="wrap_content" - android:gravity="center_vertical" > - <ImageView - android:id="@+id/wifi_in" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_activity_down" - android:visibility="gone" - android:paddingEnd="2dp" - /> - <ImageView - android:id="@+id/wifi_out" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_activity_up" - android:paddingEnd="2dp" - android:visibility="gone" - /> - </FrameLayout> - <FrameLayout - android:id="@+id/wifi_combo" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:gravity="center_vertical" > - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@+id/wifi_signal" - android:layout_height="@dimen/status_bar_wifi_signal_size" - android:layout_width="@dimen/status_bar_wifi_signal_size" /> - </FrameLayout> + <include layout="@layout/status_bar_wifi_group_inner" /> - <View - android:id="@+id/wifi_signal_spacer" - android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" - android:layout_height="4dp" - android:visibility="gone" /> - - <!-- Looks like CarStatusBar uses this... --> - <ViewStub - android:id="@+id/connected_device_signals_stub" - android:layout="@layout/connected_device_signal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - - <View - android:id="@+id/wifi_airplane_spacer" - android:layout_width="@dimen/status_bar_airplane_spacer_width" - android:layout_height="4dp" - android:visibility="gone" - /> - </com.android.keyguard.AlphaOptimizedLinearLayout> -</com.android.systemui.statusbar.StatusBarWifiView> +</com.android.systemui.statusbar.StatusBarWifiView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml new file mode 100644 index 000000000000..0ea0653ab89f --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <com.android.keyguard.AlphaOptimizedLinearLayout + android:id="@+id/wifi_group" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:layout_marginStart="2.5dp" + > + <FrameLayout + android:id="@+id/inout_container" + android:layout_height="17dp" + android:layout_width="wrap_content" + android:gravity="center_vertical" > + <ImageView + android:id="@+id/wifi_in" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_down" + android:visibility="gone" + android:paddingEnd="2dp" + /> + <ImageView + android:id="@+id/wifi_out" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_up" + android:paddingEnd="2dp" + android:visibility="gone" + /> + </FrameLayout> + <FrameLayout + android:id="@+id/wifi_combo" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="center_vertical" > + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/wifi_signal" + android:layout_height="@dimen/status_bar_wifi_signal_size" + android:layout_width="@dimen/status_bar_wifi_signal_size" /> + </FrameLayout> + + <View + android:id="@+id/wifi_signal_spacer" + android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" + android:layout_height="4dp" + android:visibility="gone" /> + + <!-- Looks like CarStatusBar uses this... --> + <ViewStub + android:id="@+id/connected_device_signals_stub" + android:layout="@layout/connected_device_signal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <View + android:id="@+id/wifi_airplane_spacer" + android:layout_width="@dimen/status_bar_airplane_spacer_width" + android:layout_height="4dp" + android:visibility="gone" + /> + </com.android.keyguard.AlphaOptimizedLinearLayout> +</merge> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7c1fdd548066..ae30089358fe 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1179,7 +1179,6 @@ <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item> <!-- Output switcher panel related dimensions --> - <dimen name="media_output_dialog_list_margin">12dp</dimen> <dimen name="media_output_dialog_list_max_height">355dp</dimen> <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen> <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen> 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 8f1959e884cf..a21a78b92d84 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 @@ -50,7 +50,12 @@ class AnimatableClockView @JvmOverloads constructor( defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - private var lastMeasureCall: CharSequence = "" + private var lastMeasureCall: CharSequence? = null + private var lastDraw: CharSequence? = null + private var lastTextUpdate: CharSequence? = null + private var lastOnTextChanged: CharSequence? = null + private var lastInvalidate: CharSequence? = null + private var lastTimeZoneChange: CharSequence? = null private val time = Calendar.getInstance() @@ -142,7 +147,6 @@ 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 @@ -151,18 +155,19 @@ class AnimatableClockView @JvmOverloads constructor( textAnimator?.updateLayout(layout) } requestLayout() + lastTextUpdate = getTimestamp() } } fun onTimeZoneChanged(timeZone: TimeZone?) { time.timeZone = timeZone refreshFormat() + lastTimeZoneChange = "${getTimestamp()} timeZone=${time.timeZone}" } @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) - lastMeasureCall = DateFormat.format(descFormat, System.currentTimeMillis()) val animator = textAnimator if (animator == null) { textAnimator = TextAnimator(layout) { invalidate() } @@ -171,13 +176,34 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } + lastMeasureCall = getTimestamp() } override fun onDraw(canvas: Canvas) { + lastDraw = getTimestamp() // intentionally doesn't call super.onDraw here or else the text will be rendered twice textAnimator?.draw(canvas) } + override fun invalidate() { + super.invalidate() + lastInvalidate = getTimestamp() + } + + private fun getTimestamp(): CharSequence { + return "${DateFormat.format("HH:mm:ss", System.currentTimeMillis())} text=$text" + } + + override fun onTextChanged( + text: CharSequence, + start: Int, + lengthBefore: Int, + lengthAfter: Int + ) { + super.onTextChanged(text, start, lengthBefore, lengthAfter) + lastOnTextChanged = "${getTimestamp()}" + } + fun setLineSpacingScale(scale: Float) { lineSpacingScale = scale setLineSpacing(0f, lineSpacingScale) @@ -370,7 +396,12 @@ class AnimatableClockView @JvmOverloads constructor( pw.println(" measuredWidth=$measuredWidth") pw.println(" measuredHeight=$measuredHeight") pw.println(" singleLineInternal=$isSingleLineInternal") + pw.println(" lastTextUpdate=$lastTextUpdate") + pw.println(" lastOnTextChanged=$lastOnTextChanged") + pw.println(" lastInvalidate=$lastInvalidate") pw.println(" lastMeasureCall=$lastMeasureCall") + pw.println(" lastDraw=$lastDraw") + pw.println(" lastTimeZoneChange=$lastTimeZoneChange") pw.println(" currText=$text") pw.println(" currTimeContextDesc=$contentDescription") pw.println(" time=$time") diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index b29dc835a6f4..22bffda33918 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -49,6 +49,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; @@ -99,6 +100,7 @@ public class RotationButtonController { private @WindowInsetsController.Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; private int mNavBarMode; + private boolean mTaskBarVisible = false; private boolean mSkipOverrideUserLockPrefsOnce; private final int mLightIconColor; private final int mDarkIconColor; @@ -422,6 +424,7 @@ public class RotationButtonController { } public void onTaskbarStateChange(boolean visible, boolean stashed) { + mTaskBarVisible = visible; if (getRotationButton() == null) { return; } @@ -438,9 +441,12 @@ public class RotationButtonController { * Return true when either the task bar is visible or it's in visual immersive mode. */ @SuppressLint("InlinedApi") - private boolean canShowRotationButton() { - return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT - || isGesturalMode(mNavBarMode); + @VisibleForTesting + boolean canShowRotationButton() { + return mIsNavigationBarShowing + || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT + || isGesturalMode(mNavBarMode) + || mTaskBarVisible; } @DrawableRes @@ -624,4 +630,3 @@ public class RotationButtonController { } } } - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 2f9bc1c59624..d75285283cbc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -54,6 +54,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; @@ -2018,12 +2019,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // in case authenticators aren't registered yet at this point: mAuthController.addCallback(new AuthController.Callback() { @Override - public void onAllAuthenticatorsRegistered() { + public void onAllAuthenticatorsRegistered( + @BiometricAuthenticator.Modality int modality) { mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE)); } @Override - public void onEnrollmentsChanged() { + public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE)); } }); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 06e1828ef9f4..d6974dfac570 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static com.android.keyguard.LockIconView.ICON_FINGERPRINT; @@ -29,6 +30,7 @@ import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.AnimatedStateListDrawable; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricSourceType; import android.os.Process; import android.os.VibrationAttributes; @@ -701,13 +703,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override - public void onAllAuthenticatorsRegistered() { - updateUdfpsConfig(); + public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { + if (modality == TYPE_FINGERPRINT) { + updateUdfpsConfig(); + } } @Override - public void onEnrollmentsChanged() { - updateUdfpsConfig(); + public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { + if (modality == TYPE_FINGERPRINT) { + updateUdfpsConfig(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java index 43b3929808b3..df65bcf9c10d 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -34,12 +35,12 @@ import javax.inject.Inject; @SysUISingleton public class ActivityIntentHelper { - private final Context mContext; + private final PackageManager mPm; @Inject public ActivityIntentHelper(Context context) { // TODO: inject a package manager, not a context. - mContext = context; + mPm = context.getPackageManager(); } /** @@ -57,6 +58,15 @@ public class ActivityIntentHelper { } /** + * @see #wouldLaunchResolverActivity(Intent, int) + */ + public boolean wouldPendingLaunchResolverActivity(PendingIntent intent, int currentUserId) { + ActivityInfo targetActivityInfo = getPendingTargetActivityInfo(intent, currentUserId, + false /* onlyDirectBootAware */); + return targetActivityInfo == null; + } + + /** * Returns info about the target Activity of a given intent, or null if the intent does not * resolve to a specific component meeting the requirements. * @@ -68,19 +78,45 @@ public class ActivityIntentHelper { */ public ActivityInfo getTargetActivityInfo(Intent intent, int currentUserId, boolean onlyDirectBootAware) { - PackageManager packageManager = mContext.getPackageManager(); - int flags = PackageManager.MATCH_DEFAULT_ONLY; + int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA; if (!onlyDirectBootAware) { flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; } - final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( + final List<ResolveInfo> appList = mPm.queryIntentActivitiesAsUser( intent, flags, currentUserId); if (appList.size() == 0) { return null; } - ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, - flags | PackageManager.GET_META_DATA, currentUserId); + if (appList.size() == 1) { + return appList.get(0).activityInfo; + } + ResolveInfo resolved = mPm.resolveActivityAsUser(intent, flags, currentUserId); + if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) { + return null; + } else { + return resolved.activityInfo; + } + } + + /** + * @see #getTargetActivityInfo(Intent, int, boolean) + */ + public ActivityInfo getPendingTargetActivityInfo(PendingIntent intent, int currentUserId, + boolean onlyDirectBootAware) { + int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA; + if (!onlyDirectBootAware) { + flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + } + final List<ResolveInfo> appList = intent.queryIntentComponents(flags); + if (appList.size() == 0) { + return null; + } + if (appList.size() == 1) { + return appList.get(0).activityInfo; + } + ResolveInfo resolved = mPm.resolveActivityAsUser(intent.getIntent(), flags, currentUserId); if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) { return null; } else { @@ -104,6 +140,17 @@ public class ActivityIntentHelper { } /** + * @see #wouldShowOverLockscreen(Intent, int) + */ + public boolean wouldPendingShowOverLockscreen(PendingIntent intent, int currentUserId) { + ActivityInfo targetActivityInfo = getPendingTargetActivityInfo(intent, + currentUserId, false /* onlyDirectBootAware */); + return targetActivityInfo != null + && (targetActivityInfo.flags & (ActivityInfo.FLAG_SHOW_WHEN_LOCKED + | ActivityInfo.FLAG_SHOW_FOR_ALL_USERS)) > 0; + } + + /** * Determines if sending the given intent would result in starting an Intent resolver activity, * instead of resolving to a specific component. * diff --git a/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java index b0eaab97c5ac..fa9a83ec9913 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java +++ b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java @@ -25,7 +25,6 @@ import android.content.pm.UserInfo; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; -import android.util.FeatureFlagUtils; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.util.NotificationChannels; @@ -59,10 +58,8 @@ public final class GuestSessionNotification { } void createPersistentNotification(UserInfo userInfo, boolean isGuestFirstLogin) { - if (!FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES) - || !userInfo.isGuest()) { - // we create a persistent notification only if enabled and only for guests + if (!userInfo.isGuest()) { + // we create a persistent notification only for guests return; } String contentText; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 47ff59cfc281..282f25104c44 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -46,6 +46,7 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; @@ -156,25 +157,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } }; - private final IFingerprintAuthenticatorsRegisteredCallback - mFingerprintAuthenticatorsRegisteredCallback = - new IFingerprintAuthenticatorsRegisteredCallback.Stub() { - @Override - public void onAllAuthenticatorsRegistered( - List<FingerprintSensorPropertiesInternal> sensors) { - mHandler.post(() -> handleAllFingerprintAuthenticatorsRegistered(sensors)); - } - }; - - private final BiometricStateListener mBiometricStateListener = - new BiometricStateListener() { - @Override - public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { - mHandler.post( - () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments)); - } - }; - @VisibleForTesting final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -249,8 +231,8 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba List<FingerprintSensorPropertiesInternal> sensors) { mExecution.assertIsMainThread(); if (DEBUG) { - Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString( - sensors.toArray())); + Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: " + + Arrays.toString(sensors.toArray())); } mAllFingerprintAuthenticatorsRegistered = true; mFpProps = sensors; @@ -292,15 +274,42 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mSidefpsController = mSidefpsControllerFactory.get(); } - mFingerprintManager.registerBiometricStateListener(mBiometricStateListener); + mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mHandler.post(() -> handleEnrollmentsChanged( + TYPE_FINGERPRINT, userId, sensorId, hasEnrollments)); + } + }); updateFingerprintLocation(); for (Callback cb : mCallbacks) { - cb.onAllAuthenticatorsRegistered(); + cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); } } - private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) { + mExecution.assertIsMainThread(); + if (DEBUG) { + Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString( + sensors.toArray())); + } + + mFaceManager.registerBiometricStateListener(new BiometricStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mHandler.post(() -> handleEnrollmentsChanged( + TYPE_FACE, userId, sensorId, hasEnrollments)); + } + }); + + for (Callback cb : mCallbacks) { + cb.onAllAuthenticatorsRegistered(TYPE_FACE); + } + } + + private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId, + boolean hasEnrollments) { mExecution.assertIsMainThread(); Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId + ", hasEnrollments: " + hasEnrollments); @@ -314,7 +323,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } } for (Callback cb : mCallbacks) { - cb.onEnrollmentsChanged(); + cb.onEnrollmentsChanged(modality); } } @@ -700,7 +709,26 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba if (mFingerprintManager != null) { mFingerprintManager.addAuthenticatorsRegisteredCallback( - mFingerprintAuthenticatorsRegisteredCallback); + new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> sensors) { + mHandler.post(() -> + handleAllFingerprintAuthenticatorsRegistered(sensors)); + } + }); + } + if (mFaceManager != null) { + mFaceManager.addAuthenticatorsRegisteredCallback( + new IFaceAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FaceSensorPropertiesInternal> sensors) { + mHandler.post(() -> + handleAllFaceAuthenticatorsRegistered(sensors)); + } + } + ); } mStableDisplaySize = mDisplayManager.getStableDisplaySize(); @@ -1116,13 +1144,13 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba * Called when authenticators are registered. If authenticators are already * registered before this call, this callback will never be triggered. */ - default void onAllAuthenticatorsRegistered() {} + default void onAllAuthenticatorsRegistered(@Modality int modality) {} /** - * Called when UDFPS enrollments have changed. This is called after boot and on changes to + * Called when enrollments have changed. This is called after boot and on changes to * enrollment. */ - default void onEnrollmentsChanged() {} + default void onEnrollmentsChanged(@Modality int modality) {} /** * Called when the biometric prompt starts showing. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 38fab8ffbfad..fd3f6007d8a9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -308,7 +308,7 @@ class AuthRippleController @Inject constructor( private val authControllerCallback = object : AuthController.Callback { - override fun onAllAuthenticatorsRegistered() { + override fun onAllAuthenticatorsRegistered(modality: Int) { updateUdfpsDependentParams() updateSensorLocation() } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 2dadf573fd12..e549a96079bd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -241,7 +241,6 @@ public abstract class SystemUIModule { notifCollection, notifPipeline, sysUiState, - dumpManager, sysuiMainExecutor)); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index a9e310d25f9c..7da2cf150147 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -16,12 +16,15 @@ package com.android.systemui.doze; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; +import android.hardware.biometrics.BiometricAuthenticator; import android.os.Handler; import android.util.Log; import android.view.Display; @@ -232,13 +235,17 @@ public class DozeScreenState implements DozeMachine.Part { private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override - public void onAllAuthenticatorsRegistered() { - updateUdfpsController(); + public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { + if (modality == TYPE_FINGERPRINT) { + updateUdfpsController(); + } } @Override - public void onEnrollmentsChanged() { - updateUdfpsController(); + public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { + if (modality == TYPE_FINGERPRINT) { + updateUdfpsController(); + } } }; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index da6c163b1eea..997a6e554364 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -16,6 +16,8 @@ package com.android.systemui.doze; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP; import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY; @@ -29,6 +31,7 @@ import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.TriggerEvent; import android.hardware.TriggerEventListener; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.display.AmbientDisplayConfiguration; import android.net.Uri; import android.os.Handler; @@ -835,13 +838,17 @@ public class DozeSensors { private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override - public void onAllAuthenticatorsRegistered() { - updateUdfpsEnrolled(); + public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { + if (modality == TYPE_FINGERPRINT) { + updateUdfpsEnrolled(); + } } @Override - public void onEnrollmentsChanged() { - updateUdfpsEnrolled(); + public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { + if (modality == TYPE_FINGERPRINT) { + updateUdfpsEnrolled(); + } } private void updateUdfpsEnrolled() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 7e4a108aadf1..823255c38a84 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -113,7 +113,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { } void setExtraStatusBarItemViews(List<View> views) { - mSystemStatusViewGroup.removeAllViews(); + removeAllStatusBarItemViews(); views.forEach(view -> mSystemStatusViewGroup.addView(view)); } @@ -121,4 +121,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { final View statusIcon = findViewById(resId); return Objects.requireNonNull(statusIcon); } + + void removeAllStatusBarItemViews() { + mSystemStatusViewGroup.removeAllViews(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 65cfae1ac14b..6f505504b186 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -192,6 +192,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDreamOverlayNotificationCountProvider.ifPresent( provider -> provider.removeCallback(mNotificationCountCallback)); mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); + mView.removeAllStatusBarItemViews(); mTouchInsetSession.clear(); mIsAttached = false; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java index 83249aa324d1..bbcab60d7ba2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java @@ -69,7 +69,7 @@ public class ComplicationTypesUpdater extends CoreStartable { }; mSecureSettings.registerContentObserverForUser( - Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, settingsObserver, UserHandle.myUserId()); settingsObserver.onChange(false); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 1f356cb3d18c..677f0a2e288b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -154,7 +154,11 @@ public class Flags { public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE = new ReleasedFlag(603, false); - public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE = new UnreleasedFlag(604, true); + public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND = + new UnreleasedFlag(604, true); + + public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND = + new UnreleasedFlag(605, true); /***************************************/ // 700 - dialer/calls diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 19c6249a12c0..c4e3d4e4c1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -251,9 +251,21 @@ object KeyguardBottomAreaViewBinder { Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface) view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId) - view.setOnClickListener { + view.isClickable = viewModel.isClickable + if (viewModel.isClickable) { + view.setOnClickListener(OnClickListener(viewModel, falsingManager)) + } else { + view.setOnClickListener(null) + } + } + + private class OnClickListener( + private val viewModel: KeyguardQuickAffordanceViewModel, + private val falsingManager: FalsingManager, + ) : View.OnClickListener { + override fun onClick(view: View) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return@setOnClickListener + return } if (viewModel.configKey != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 01d5e5c493ce..e3ebac60febb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import androidx.annotation.VisibleForTesting import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -37,6 +38,23 @@ constructor( private val bottomAreaInteractor: KeyguardBottomAreaInteractor, private val burnInHelperWrapper: BurnInHelperWrapper, ) { + /** + * Whether quick affordances are "opaque enough" to be considered visible to and interactive by + * the user. If they are not interactive, user input should not be allowed on them. + * + * Note that there is a margin of error, where we allow very, very slightly transparent views to + * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the + * error margin of floating point arithmetic. + * + * A view that is visible but with an alpha of less than our threshold either means it's not + * fully done fading in or is fading/faded out. Either way, it should not be + * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987. + */ + private val areQuickAffordancesFullyOpaque: Flow<Boolean> = + bottomAreaInteractor.alpha + .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD } + .distinctUntilChanged() + /** An observable for the view-model of the "start button" quick affordance. */ val startButton: Flow<KeyguardQuickAffordanceViewModel> = button(KeyguardQuickAffordancePosition.BOTTOM_START) @@ -77,14 +95,19 @@ constructor( return combine( quickAffordanceInteractor.quickAffordance(position), bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(), - ) { model, animateReveal -> - model.toViewModel(animateReveal) + areQuickAffordancesFullyOpaque, + ) { model, animateReveal, isFullyOpaque -> + model.toViewModel( + animateReveal = animateReveal, + isClickable = isFullyOpaque, + ) } .distinctUntilChanged() } private fun KeyguardQuickAffordanceModel.toViewModel( animateReveal: Boolean, + isClickable: Boolean, ): KeyguardQuickAffordanceViewModel { return when (this) { is KeyguardQuickAffordanceModel.Visible -> @@ -100,8 +123,20 @@ constructor( animationController = parameters.animationController, ) }, + isClickable = isClickable, ) is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() } } + + companion object { + // We select a value that's less than 1.0 because we want floating point math precision to + // not be a factor in determining whether the affordance UI is fully opaque. The number we + // choose needs to be close enough 1.0 such that the user can't easily tell the difference + // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same + // time, we don't want the number to be too close to 1.0 such that there is a chance that we + // never treat the affordance UI as "fully opaque" as that would risk making it forever not + // clickable. + @VisibleForTesting const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt index 985ab623764a..b1de27d262cf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt @@ -31,6 +31,7 @@ data class KeyguardQuickAffordanceViewModel( val icon: ContainedDrawable = ContainedDrawable.WithResource(0), @StringRes val contentDescriptionResourceId: Int = 0, val onClicked: (OnClickedParameters) -> Unit = {}, + val isClickable: Boolean = false, ) { data class OnClickedParameters( val configKey: KClass<out KeyguardQuickAffordanceConfig>, 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 e360d10d9362..ee5956105b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -16,6 +16,7 @@ package com.android.systemui.media.dialog; +import android.annotation.DrawableRes; import android.content.res.ColorStateList; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; @@ -42,9 +43,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final String TAG = "MediaOutputAdapter"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private ViewGroup mConnectedItem; - private boolean mIncludeDynamicGroup; - public MediaOutputAdapter(MediaOutputController controller) { super(controller); setHasStableIds(true); @@ -102,141 +100,90 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { super.onBind(device, topMargin, bottomMargin, position); boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice(); - final boolean currentlyConnected = !mIncludeDynamicGroup - && isCurrentlyConnected(device); + final boolean currentlyConnected = isCurrentlyConnected(device); boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE; - if (currentlyConnected) { - mConnectedItem = mContainerLayout; - } - mCheckBox.setVisibility(View.GONE); - mStatusIcon.setVisibility(View.GONE); - mEndTouchArea.setVisibility(View.GONE); - mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - mContainerLayout.setOnClickListener(null); - mContainerLayout.setContentDescription(null); - mTitleText.setTextColor(mController.getColorItemContent()); - mSubTitleText.setTextColor(mController.getColorItemContent()); - mTwoLineTitleText.setTextColor(mController.getColorItemContent()); - mSeekBar.getProgressDrawable().setColorFilter( - new PorterDuffColorFilter(mController.getColorSeekbarProgress(), - PorterDuff.Mode.SRC_IN)); if (mCurrentActivePosition == position) { mCurrentActivePosition = -1; } - if (mController.isTransferring()) { + if (mController.isAnyDeviceTransferring()) { if (device.getState() == MediaDeviceState.STATE_CONNECTING && !mController.hasAdjustVolumeUserRestriction()) { setUpDeviceIcon(device); - mProgressBar.getIndeterminateDrawable().setColorFilter( - new PorterDuffColorFilter( - mController.getColorItemContent(), - PorterDuff.Mode.SRC_IN)); - setSingleLineLayout(getItemTitle(device), true /* bFocused */, - false /* showSeekBar*/, - true /* showProgressBar */, false /* showStatus */); + updateProgressBarColor(); + setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/, + true /* showProgressBar */, false /* showCheckBox */, + false /* showEndTouchArea */); } else { setUpDeviceIcon(device); - setSingleLineLayout(getItemTitle(device), false /* bFocused */); + setSingleLineLayout(getItemTitle(device)); } } else { // Set different layout for each device if (device.isMutingExpectedDevice() && !mController.isCurrentConnectedDeviceRemote()) { - mTitleIcon.setImageDrawable( - mContext.getDrawable(R.drawable.media_output_icon_volume)); - mTitleIcon.setColorFilter(mController.getColorItemContent()); - mTitleText.setTextColor(mController.getColorItemContent()); - setSingleLineLayout(getItemTitle(device), true /* bFocused */, - false /* showSeekBar */, - false /* showProgressBar */, false /* showStatus */); + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); initMutingExpectedDevice(); mCurrentActivePosition = position; - mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); + updateContainerClickListener(v -> onItemClick(v, device)); + setSingleLineLayout(getItemTitle(device)); } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); - mStatusIcon.setImageDrawable( - mContext.getDrawable(R.drawable.media_output_status_failed)); - mStatusIcon.setColorFilter(mController.getColorItemContent()); - setTwoLineLayout(device, false /* bFocused */, - false /* showSeekBar */, false /* showProgressBar */, - true /* showSubtitle */, true /* showStatus */); + updateConnectionFailedStatusIcon(); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); - mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); + updateContainerClickListener(v -> onItemClick(v, device)); + setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */, + false /* showProgressBar */, true /* showSubtitle */, + true /* showStatus */); } else if (device.getState() == MediaDeviceState.STATE_GROUPING) { setUpDeviceIcon(device); - mProgressBar.getIndeterminateDrawable().setColorFilter( - new PorterDuffColorFilter( - mController.getColorItemContent(), - PorterDuff.Mode.SRC_IN)); - setSingleLineLayout(getItemTitle(device), true /* bFocused */, - false /* showSeekBar*/, - true /* showProgressBar */, false /* showStatus */); + updateProgressBarColor(); + setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/, + true /* showProgressBar */, false /* showCheckBox */, + false /* showEndTouchArea */); } else if (mController.getSelectedMediaDevice().size() > 1 && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) { boolean isDeviceDeselectable = isDeviceIncluded( mController.getDeselectableMediaDevice(), device); - mTitleText.setTextColor(mController.getColorItemContent()); - mTitleIcon.setImageDrawable( - mContext.getDrawable(R.drawable.media_output_icon_volume)); - mTitleIcon.setColorFilter(mController.getColorItemContent()); - setSingleLineLayout(getItemTitle(device), true /* bFocused */, - true /* showSeekBar */, - false /* showProgressBar */, false /* showStatus */); + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + updateGroupableCheckBox(true, isDeviceDeselectable, device); + updateEndClickArea(device, isDeviceDeselectable); setUpContentDescriptionForView(mContainerLayout, false, device); - mCheckBox.setOnCheckedChangeListener(null); - mCheckBox.setVisibility(View.VISIBLE); - mCheckBox.setChecked(true); - mCheckBox.setOnCheckedChangeListener(isDeviceDeselectable - ? (buttonView, isChecked) -> onGroupActionTriggered(false, device) - : null); - mCheckBox.setEnabled(isDeviceDeselectable); - setCheckBoxColor(mCheckBox, mController.getColorItemContent()); + setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + false /* showProgressBar */, true /* showCheckBox */, + true /* showEndTouchArea */); initSeekbar(device, isCurrentSeekbarInvisible); - mEndTouchArea.setVisibility(View.VISIBLE); - mEndTouchArea.setOnClickListener(null); - mEndTouchArea.setOnClickListener( - isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null); - mEndTouchArea.setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_YES); - setUpContentDescriptionForView(mEndTouchArea, true, device); } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) { if (isMutingExpectedDeviceExist && !mController.isCurrentConnectedDeviceRemote()) { // mark as disconnected and set special click listener setUpDeviceIcon(device); - setSingleLineLayout(getItemTitle(device), false /* bFocused */); - mContainerLayout.setOnClickListener(v -> cancelMuteAwaitConnection()); + updateContainerClickListener(v -> cancelMuteAwaitConnection()); + setSingleLineLayout(getItemTitle(device)); } else { - mTitleIcon.setImageDrawable( - mContext.getDrawable(R.drawable.media_output_icon_volume)); - mTitleIcon.setColorFilter(mController.getColorItemContent()); - mTitleText.setTextColor(mController.getColorItemContent()); - setSingleLineLayout(getItemTitle(device), true /* bFocused */, - true /* showSeekBar */, - false /* showProgressBar */, false /* showStatus */); - initSeekbar(device, isCurrentSeekbarInvisible); + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); setUpContentDescriptionForView(mContainerLayout, false, device); mCurrentActivePosition = position; + setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + false /* showProgressBar */, false /* showCheckBox */, + false /* showEndTouchArea */); + initSeekbar(device, isCurrentSeekbarInvisible); } } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { setUpDeviceIcon(device); - mCheckBox.setOnCheckedChangeListener(null); - mCheckBox.setVisibility(View.VISIBLE); - mCheckBox.setChecked(false); - mCheckBox.setOnCheckedChangeListener( - (buttonView, isChecked) -> onGroupActionTriggered(true, device)); - mEndTouchArea.setVisibility(View.VISIBLE); - mContainerLayout.setOnClickListener(v -> onGroupActionTriggered(true, device)); - setCheckBoxColor(mCheckBox, mController.getColorItemContent()); - setSingleLineLayout(getItemTitle(device), false /* bFocused */, - false /* showSeekBar */, - false /* showProgressBar */, false /* showStatus */); + updateGroupableCheckBox(false, true, device); + updateContainerClickListener(v -> onGroupActionTriggered(true, device)); + setSingleLineLayout(getItemTitle(device), false /* showSeekBar */, + false /* showProgressBar */, true /* showCheckBox */, + true /* showEndTouchArea */); } else { setUpDeviceIcon(device); - setSingleLineLayout(getItemTitle(device), false /* bFocused */); - mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); + setSingleLineLayout(getItemTitle(device)); + updateContainerClickListener(v -> onItemClick(v, device)); } } } @@ -248,15 +195,56 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { ColorStateList(states, colors)); } + private void updateConnectionFailedStatusIcon() { + mStatusIcon.setImageDrawable( + mContext.getDrawable(R.drawable.media_output_status_failed)); + mStatusIcon.setColorFilter(mController.getColorItemContent()); + } + + private void updateProgressBarColor() { + mProgressBar.getIndeterminateDrawable().setColorFilter( + new PorterDuffColorFilter( + mController.getColorItemContent(), + PorterDuff.Mode.SRC_IN)); + } + + public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) { + mEndTouchArea.setOnClickListener(null); + mEndTouchArea.setOnClickListener( + isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null); + mEndTouchArea.setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_YES); + setUpContentDescriptionForView(mEndTouchArea, true, device); + } + + private void updateGroupableCheckBox(boolean isSelected, boolean isGroupable, + MediaDevice device) { + mCheckBox.setOnCheckedChangeListener(null); + mCheckBox.setChecked(isSelected); + mCheckBox.setOnCheckedChangeListener( + isGroupable ? (buttonView, isChecked) -> onGroupActionTriggered(!isSelected, + device) : null); + mCheckBox.setEnabled(isGroupable); + setCheckBoxColor(mCheckBox, mController.getColorItemContent()); + } + + private void updateTitleIcon(@DrawableRes int id, int color) { + mTitleIcon.setImageDrawable(mContext.getDrawable(id)); + mTitleIcon.setColorFilter(color); + } + + private void updateContainerClickListener(View.OnClickListener listener) { + mContainerLayout.setOnClickListener(listener); + } + @Override void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) { if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { mTitleText.setTextColor(mController.getColorItemContent()); mCheckBox.setVisibility(View.GONE); - setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new), - false /* bFocused */); - final Drawable d = mContext.getDrawable(R.drawable.ic_add); - mTitleIcon.setImageDrawable(d); + setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new)); + final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add); + mTitleIcon.setImageDrawable(addDrawable); mTitleIcon.setColorFilter(mController.getColorItemContent()); mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); } @@ -273,7 +261,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } private void onItemClick(View view, MediaDevice device) { - if (mController.isTransferring()) { + if (mController.isAnyDeviceTransferring()) { return; } if (isCurrentlyConnected(device)) { 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 43b0287c164e..3f7b2261ea52 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -63,8 +63,6 @@ public abstract class MediaOutputBaseAdapter extends protected final MediaOutputController mController; - private int mMargin; - Context mContext; View mHolderView; boolean mIsDragging; @@ -82,8 +80,6 @@ public abstract class MediaOutputBaseAdapter extends public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { mContext = viewGroup.getContext(); - mMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_list_margin); mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item, viewGroup, false); @@ -168,16 +164,28 @@ public abstract class MediaOutputBaseAdapter extends void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { mDeviceId = device.getId(); + mCheckBox.setVisibility(View.GONE); + mStatusIcon.setVisibility(View.GONE); + mEndTouchArea.setVisibility(View.GONE); + mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mContainerLayout.setOnClickListener(null); + mContainerLayout.setContentDescription(null); + mTitleText.setTextColor(mController.getColorItemContent()); + mSubTitleText.setTextColor(mController.getColorItemContent()); + mTwoLineTitleText.setTextColor(mController.getColorItemContent()); + mSeekBar.getProgressDrawable().setColorFilter( + new PorterDuffColorFilter(mController.getColorSeekbarProgress(), + PorterDuff.Mode.SRC_IN)); } abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin); - void setSingleLineLayout(CharSequence title, boolean bFocused) { - setSingleLineLayout(title, bFocused, false, false, false); + void setSingleLineLayout(CharSequence title) { + setSingleLineLayout(title, false, false, false, false); } - void setSingleLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar, - boolean showProgressBar, boolean showStatus) { + void setSingleLineLayout(CharSequence title, boolean showSeekBar, + boolean showProgressBar, boolean showCheckBox, boolean showEndTouchArea) { mTwoLineLayout.setVisibility(View.GONE); boolean isActive = showSeekBar || showProgressBar; if (!mCornerAnimator.isRunning()) { @@ -188,10 +196,6 @@ public abstract class MediaOutputBaseAdapter extends .mutate() : mContext.getDrawable( R.drawable.media_output_item_background) .mutate(); - backgroundDrawable.setColorFilter(new PorterDuffColorFilter( - isActive ? mController.getColorConnectedItemBackground() - : mController.getColorItemBackground(), - PorterDuff.Mode.SRC_IN)); mItemLayout.setBackground(backgroundDrawable); if (showSeekBar) { final ClipDrawable clipDrawable = @@ -201,27 +205,21 @@ public abstract class MediaOutputBaseAdapter extends (GradientDrawable) clipDrawable.getDrawable(); progressDrawable.setCornerRadius(mController.getActiveRadius()); } - } else { - mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter( - isActive ? mController.getColorConnectedItemBackground() - : mController.getColorItemBackground(), - PorterDuff.Mode.SRC_IN)); } + mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter( + isActive ? mController.getColorConnectedItemBackground() + : mController.getColorItemBackground(), + PorterDuff.Mode.SRC_IN)); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSeekBar.setAlpha(1); mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); if (!showSeekBar) { mSeekBar.resetVolume(); } - mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); mTitleText.setText(title); mTitleText.setVisibility(View.VISIBLE); - } - - void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, - boolean showProgressBar, boolean showSubtitle) { - setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle, - false); + mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE); + mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE); } void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, @@ -230,12 +228,6 @@ public abstract class MediaOutputBaseAdapter extends showStatus); } - void setTwoLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar, - boolean showProgressBar, boolean showSubtitle) { - setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle, - false); - } - private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused, boolean showSeekBar, boolean showProgressBar, boolean showSubtitle, boolean showStatus) { @@ -254,20 +246,11 @@ public abstract class MediaOutputBaseAdapter extends mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); mTwoLineTitleText.setTranslationY(0); - if (device == null) { - mTwoLineTitleText.setText(title); - } else { - mTwoLineTitleText.setText(getItemTitle(device)); - } - - if (bFocused) { - mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString( - com.android.internal.R.string.config_headlineFontFamilyMedium), - Typeface.NORMAL)); - } else { - mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString( - com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL)); - } + mTwoLineTitleText.setText(device == null ? title : getItemTitle(device)); + mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString( + bFocused ? com.android.internal.R.string.config_headlineFontFamilyMedium + : com.android.internal.R.string.config_headlineFontFamily), + Typeface.NORMAL)); } void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) { @@ -327,35 +310,6 @@ public abstract class MediaOutputBaseAdapter extends mItemLayout.setBackground(backgroundDrawable); } - void initSessionSeekbar() { - disableSeekBar(); - mSeekBar.setMax(mController.getSessionVolumeMax()); - mSeekBar.setMin(0); - final int currentVolume = mController.getSessionVolume(); - if (mSeekBar.getProgress() != currentVolume) { - mSeekBar.setProgress(currentVolume, true); - } - mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (!fromUser) { - return; - } - mController.adjustSessionVolume(progress); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - mIsDragging = true; - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - mIsDragging = false; - } - }); - } - private void animateCornerAndVolume(int fromProgress, int toProgress) { final GradientDrawable layoutBackgroundDrawable = (GradientDrawable) mItemLayout.getBackground(); 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 f7d80e0b1dbd..96817c9bf32d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -411,7 +411,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, device.getId()); boolean isSelectedDeviceInGroup = getSelectedMediaDevice().size() > 1 && getSelectedMediaDevice().contains(device); - return (!hasAdjustVolumeUserRestriction() && isConnected && !isTransferring()) + return (!hasAdjustVolumeUserRestriction() && isConnected && !isAnyDeviceTransferring()) || isSelectedDeviceInGroup; } @@ -708,7 +708,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, UserHandle.of(UserHandle.myUserId())); } - boolean isTransferring() { + boolean isAnyDeviceTransferring() { synchronized (mMediaDevicesLock) { for (MediaDevice device : mMediaDevices) { if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { 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 2278938c398e..3a0ac1b7d9b0 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 @@ -226,7 +226,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( appIconView.contentDescription = appNameOverride ?: iconInfo.iconName appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon) - return appIconView.contentDescription.toString() + return appIconView.contentDescription } /** 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 196ea222e50d..00a22f20e94d 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 @@ -141,12 +141,13 @@ class MediaTttChipControllerReceiver @Inject constructor( override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) { super.updateChipView(newChipInfo, currentChipView) - setIcon( + val iconName = setIcon( currentChipView, newChipInfo.routeInfo.clientPackageName, newChipInfo.appIconDrawableOverride, newChipInfo.appNameOverride ) + currentChipView.contentDescription = iconName } override fun animateChipIn(chipView: ViewGroup) { @@ -159,6 +160,8 @@ class MediaTttChipControllerReceiver @Inject constructor( .alpha(1f) .setDuration(5.frames) .start() + // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. + appIconView.postOnAnimation { chipView.requestAccessibilityFocus() } startRipple(chipView.requireViewById(R.id.ripple)) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index eeb1010693fc..2a6cf66995ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -80,7 +80,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader FeatureFlags featureFlags, VariableDateViewController.Factory variableDateViewControllerFactory, BatteryMeterViewController batteryMeterViewController, - StatusBarContentInsetsProvider statusBarContentInsetsProvider) { + StatusBarContentInsetsProvider statusBarContentInsetsProvider, + StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory) { super(view); mPrivacyIconsController = headerPrivacyIconsController; mStatusBarIconController = statusBarIconController; @@ -103,7 +104,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mView.requireViewById(R.id.date_clock) ); - mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags); + mIconManager = tintedIconManagerFactory.create(mIconContainer); mDemoModeReceiver = new ClockDemoModeReceiver(mClockView); mColorExtractor = colorExtractor; mOnColorsChangedListener = (extractor, which) -> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 948fb1428780..60380064e098 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -129,6 +129,15 @@ class QSLogger @Inject constructor( }) } + fun logInternetTileUpdate(lastType: Int, callback: String) { + log(VERBOSE, { + int1 = lastType + str1 = callback + }, { + "mLastTileState=$int1, Callback=$str1." + }) + } + fun logTileUpdated(tileSpec: String, state: QSTile.State) { log(VERBOSE, { str1 = tileSpec diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 170fecf1c8fd..ae464771bf48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -369,11 +369,10 @@ public class InternetTile extends QSTileImpl<SignalState> { mWifiInfo.mNoDefaultNetwork = noDefaultNetwork; mWifiInfo.mNoValidatedNetwork = noValidatedNetwork; mWifiInfo.mNoNetworksAvailable = noNetworksAvailable; - if (mLastTileState == LAST_STATE_WIFI) { - refreshState(mWifiInfo); - } else { - refreshState(mCellularInfo); + if (!noDefaultNetwork) { + return; } + refreshState(mWifiInfo); } @Override @@ -388,6 +387,7 @@ public class InternetTile extends QSTileImpl<SignalState> { @Override protected void handleUpdateState(SignalState state, Object arg) { + mQSLogger.logInternetTileUpdate(mLastTileState, arg == null ? "null" : arg.toString()); if (arg instanceof CellularCallbackInfo) { mLastTileState = LAST_STATE_CELLULAR; handleUpdateCellularState(state, arg); @@ -605,4 +605,9 @@ public class InternetTile extends QSTileImpl<SignalState> { pw.print(" "); pw.println("mLastTileState=" + mLastTileState); pw.print(" "); pw.println("mSignalCallback=" + mSignalCallback.toString()); } + + // For testing usage only. + protected int getLastTileState() { + return mLastTileState; + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index a8993bc274e4..8b37aab87665 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -70,7 +70,7 @@ import javax.inject.Inject; public class TakeScreenshotService extends Service { private static final String TAG = logTag(TakeScreenshotService.class); - private ScreenshotController mScreenshot; + private final ScreenshotController mScreenshot; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; @@ -152,10 +152,7 @@ public class TakeScreenshotService extends Service { if (DEBUG_SERVICE) { Log.d(TAG, "onUnbind"); } - if (mScreenshot != null) { - mScreenshot.removeWindow(); - mScreenshot = null; - } + mScreenshot.removeWindow(); unregisterReceiver(mCloseSystemDialogs); return false; } @@ -163,10 +160,7 @@ public class TakeScreenshotService extends Service { @Override public void onDestroy() { super.onDestroy(); - if (mScreenshot != null) { - mScreenshot.onDestroy(); - mScreenshot = null; - } + mScreenshot.onDestroy(); if (DEBUG_SERVICE) { Log.d(TAG, "onDestroy"); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 5e908d9cd29f..1558ac533137 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -76,6 +76,6 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { * Notifies that the current user's profiles have changed. */ @JvmDefault - fun onProfilesChanged(profiles: List<UserInfo>) {} + fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {} } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 0f9ac360cbe1..fab70fc2eebd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -77,6 +77,7 @@ import javax.inject.Named class LargeScreenShadeHeaderController @Inject constructor( @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View, private val statusBarIconController: StatusBarIconController, + private val tintedIconManagerFactory: StatusBarIconController.TintedIconManager.Factory, private val privacyIconsController: HeaderPrivacyIconsController, private val insetsProvider: StatusBarContentInsetsProvider, private val configurationController: ConfigurationController, @@ -259,7 +260,7 @@ class LargeScreenShadeHeaderController @Inject constructor( batteryMeterViewController.ignoreTunerUpdates() batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) - iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags) + iconManager = tintedIconManagerFactory.create(iconContainer) iconManager.setTint( Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary) ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7a4c87766114..9ca09ed6d70e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -54,7 +54,6 @@ import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; @@ -3752,13 +3751,12 @@ public final class NotificationPanelViewController extends PanelViewController { * * @param dozing {@code true} when dozing. * @param animate if transition should be animated. - * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor. */ - public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) { + public void setDozing(boolean dozing, boolean animate) { if (dozing == mDozing) return; mView.setDozing(dozing); mDozing = dozing; - mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation); + mNotificationStackScrollLayoutController.setDozing(mDozing, animate); mKeyguardBottomArea.setDozing(mDozing, animate); mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate); mKeyguardStatusBarViewController.setDozing(mDozing); @@ -4683,7 +4681,7 @@ public final class NotificationPanelViewController extends PanelViewController { * change. */ public void showAodUi() { - setDozing(true /* dozing */, false /* animate */, null); + setDozing(true /* dozing */, false /* animate */); mStatusBarStateController.setUpcomingState(KEYGUARD); mEntryManager.updateNotifications("showAodUi"); mStatusBarStateListener.onStateChanged(KEYGUARD); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index 0898d6329f75..a72b7f167d95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -24,6 +24,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.view.accessibility.AccessibilityEvent; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; @@ -49,7 +50,8 @@ public abstract class AlertingNotificationManager { protected int mMinimumDisplayTime; protected int mAutoDismissNotificationDecay; - private final Handler mHandler; + @VisibleForTesting + public Handler mHandler; /** * Called when posting a new notification that should alert the user and appear on screen. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt new file mode 100644 index 000000000000..4d53064d047d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt @@ -0,0 +1,34 @@ +/* + * 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 + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout + +/** + * A temporary base class that's shared between our old status bar wifi view implementation + * ([StatusBarWifiView]) and our new status bar wifi view implementation + * ([ModernStatusBarWifiView]). + * + * Once our refactor is over, we should be able to delete this go-between class and the old view + * class. + */ +abstract class BaseStatusBarWifiView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttrs: Int = 0, +) : FrameLayout(context, attrs, defStyleAttrs), StatusIconDisplayable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 0280e0b729a9..c04bc8289f81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -303,11 +303,6 @@ public class StatusBarStateControllerImpl implements } @Override - public void setDozeAmount(float dozeAmount, boolean animated) { - setAndInstrumentDozeAmount(null, dozeAmount, animated); - } - - @Override public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) { if (mDarkAnimator != null && mDarkAnimator.isRunning()) { if (animated && mDozeAmountTarget == dozeAmount) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java index a6986d797833..5aee62e3e89f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java @@ -28,7 +28,6 @@ import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -41,8 +40,7 @@ import java.util.ArrayList; /** * Start small: StatusBarWifiView will be able to layout from a WifiIconState */ -public class StatusBarWifiView extends FrameLayout implements DarkReceiver, - StatusIconDisplayable { +public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver { private static final String TAG = "StatusBarWifiView"; /// Used to show etc dots @@ -80,11 +78,6 @@ public class StatusBarWifiView extends FrameLayout implements DarkReceiver, super(context, attrs, defStyleAttr); } - public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public void setSlot(String slot) { mSlot = slot; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index 2b3190159ecd..2cc77384fb2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -99,14 +99,6 @@ public interface SysuiStatusBarStateController extends StatusBarStateController boolean setIsDozing(boolean isDozing); /** - * Changes the current doze amount. - * - * @param dozeAmount New doze/dark amount. - * @param animated If change should be animated or not. This will cancel current animations. - */ - void setDozeAmount(float dozeAmount, boolean animated); - - /** * Changes the current doze amount, also starts the * {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java index 3c449ad270ef..7097568c4adf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java @@ -23,7 +23,7 @@ import com.android.settingslib.SignalIcon.IconGroup; /** */ public class WifiIcons { - static final int[] WIFI_FULL_ICONS = { + public static final int[] WIFI_FULL_ICONS = { com.android.internal.R.drawable.ic_wifi_signal_0, com.android.internal.R.drawable.ic_wifi_signal_1, com.android.internal.R.drawable.ic_wifi_signal_2, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9ad906c83e10..855390d75ff8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -20,7 +20,6 @@ import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_ import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -236,11 +235,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ private boolean mIsHeadsUp; - /** - * Whether or not the notification should be redacted on the lock screen, i.e has sensitive - * content which should be redacted on the lock screen. - */ - private boolean mNeedsRedaction; private boolean mLastChronometerRunning = true; private ViewStub mChildrenContainerStub; private GroupMembershipManager mGroupMembershipManager; @@ -1502,23 +1496,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mUseIncreasedHeadsUpHeight = use; } - /** @deprecated TODO: Remove this when the old pipeline code is removed. */ - @Deprecated - public void setNeedsRedaction(boolean needsRedaction) { - if (mNeedsRedaction != needsRedaction) { - mNeedsRedaction = needsRedaction; - if (!isRemoved()) { - RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); - if (needsRedaction) { - params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); - } else { - params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); - } - mRowContentBindStage.requestRebind(mEntry, null /* callback */); - } - } - } - public interface ExpansionLogger { void logNotificationExpansion(String key, boolean userAction, boolean expanded); } 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 952bafbe6eb3..79d883b32a98 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 @@ -42,7 +42,6 @@ import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; import android.provider.Settings; @@ -4405,8 +4404,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * See {@link AmbientState#setDozing}. */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setDozing(boolean dozing, boolean animate, - @Nullable PointF touchWakeUpScreenLocation) { + public void setDozing(boolean dozing, boolean animate) { if (mAmbientState.isDozing() == dozing) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9998fe41b775..3f6586c37b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -33,7 +33,6 @@ import static com.android.systemui.statusbar.phone.NotificationIconAreaControlle import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; -import android.graphics.PointF; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; @@ -1235,8 +1234,8 @@ public class NotificationStackScrollLayoutController { mView.setAnimationsEnabled(enabled); } - public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) { - mView.setDozing(dozing, animate, wakeUpTouchLocation); + public void setDozing(boolean dozing, boolean animate) { + mView.setDozing(dozing, animate); } public void setPulsing(boolean pulsing, boolean animatePulse) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index b0a5adb62339..e444e0a1e2c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -67,7 +67,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Point; -import android.graphics.PointF; import android.hardware.devicestate.DeviceStateManager; import android.metrics.LogMaker; import android.net.Uri; @@ -446,7 +445,6 @@ public class CentralSurfacesImpl extends CoreStartable implements @VisibleForTesting DozeServiceHost mDozeServiceHost; private boolean mWakeUpComingFromTouch; - private PointF mWakeUpTouchLocation; private LightRevealScrim mLightRevealScrim; private PowerButtonReveal mPowerButtonReveal; @@ -603,8 +601,6 @@ public class CentralSurfacesImpl extends CoreStartable implements private int mLastCameraLaunchSource; protected PowerManager.WakeLock mGestureWakeLock; - private final int[] mTmpInt2 = new int[2]; - // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. private int mLastLoggedStateFingerprint; private boolean mIsLaunchingActivityOverLockscreen; @@ -1430,16 +1426,6 @@ public class CentralSurfacesImpl extends CoreStartable implements mPowerManager.wakeUp( time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why); mWakeUpComingFromTouch = true; - - // NOTE, the incoming view can sometimes be the entire container... unsure if - // this location is valuable enough - if (where != null) { - where.getLocationInWindow(mTmpInt2); - mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2, - mTmpInt2[1] + where.getHeight() / 2); - } else { - mWakeUpTouchLocation = new PointF(-1, -1); - } mFalsingCollector.onScreenOnFromTouch(); } } @@ -1955,7 +1941,6 @@ public class CentralSurfacesImpl extends CoreStartable implements PowerManager.WAKE_REASON_APPLICATION, "com.android.systemui:full_screen_intent"); mWakeUpComingFromTouch = false; - mWakeUpTouchLocation = null; } } @@ -3225,7 +3210,7 @@ public class CentralSurfacesImpl extends CoreStartable implements || (mDozing && mDozeParameters.shouldControlScreenOff() && visibleNotOccludedOrWillBe); - mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation); + mNotificationPanelViewController.setDozing(mDozing, animate); updateQsExpansionEnabled(); Trace.endSection(); } @@ -3556,7 +3541,6 @@ public class CentralSurfacesImpl extends CoreStartable implements mLaunchCameraWhenFinishedWaking = false; mDeviceInteractive = false; mWakeUpComingFromTouch = false; - mWakeUpTouchLocation = null; updateVisibleToUser(); updateNotificationPanelTouchState(); @@ -4065,7 +4049,7 @@ public class CentralSurfacesImpl extends CoreStartable implements final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback, @Nullable ActivityLaunchAnimator.Controller animationController) { final boolean willLaunchResolverActivity = intent.isActivity() - && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), + && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent, mLockscreenUserManager.getCurrentUserId()); boolean animate = !willLaunchResolverActivity diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index fc8e7d5f6aa2..deb6150f773b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -225,6 +225,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da public void addDemoWifiView(WifiIconState state) { Log.d(TAG, "addDemoWifiView: "); + // TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}. StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot); int viewIndex = getChildCount(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 30b640b583e6..ed186ab6a10b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -40,6 +40,7 @@ import com.android.systemui.demomode.DemoModeCommandReceiver; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.BaseStatusBarWifiView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarMobileView; import com.android.systemui.statusbar.StatusBarWifiView; @@ -47,12 +48,16 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; +import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel; import com.android.systemui.util.Assert; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Provider; public interface StatusBarIconController { @@ -128,8 +133,12 @@ public interface StatusBarIconController { private final DarkIconDispatcher mDarkIconDispatcher; private int mIconHPadding; - public DarkIconManager(LinearLayout linearLayout, FeatureFlags featureFlags) { - super(linearLayout, featureFlags); + public DarkIconManager( + LinearLayout linearLayout, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); mIconHPadding = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_padding); mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); @@ -183,14 +192,40 @@ public interface StatusBarIconController { mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons); super.exitDemoMode(); } + + @SysUISingleton + public static class Factory { + private final FeatureFlags mFeatureFlags; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; + private final Provider<WifiViewModel> mWifiViewModelProvider; + + @Inject + public Factory( + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + mFeatureFlags = featureFlags; + mStatusBarPipelineFlags = statusBarPipelineFlags; + mWifiViewModelProvider = wifiViewModelProvider; + } + + public DarkIconManager create(LinearLayout group) { + return new DarkIconManager( + group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider); + } + } } /** */ class TintedIconManager extends IconManager { private int mColor; - public TintedIconManager(ViewGroup group, FeatureFlags featureFlags) { - super(group, featureFlags); + public TintedIconManager( + ViewGroup group, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); } @Override @@ -223,14 +258,22 @@ public interface StatusBarIconController { @SysUISingleton public static class Factory { private final FeatureFlags mFeatureFlags; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; + private final Provider<WifiViewModel> mWifiViewModelProvider; @Inject - public Factory(FeatureFlags featureFlags) { + public Factory( + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { mFeatureFlags = featureFlags; + mStatusBarPipelineFlags = statusBarPipelineFlags; + mWifiViewModelProvider = wifiViewModelProvider; } public TintedIconManager create(ViewGroup group) { - return new TintedIconManager(group, mFeatureFlags); + return new TintedIconManager( + group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider); } } } @@ -239,8 +282,10 @@ public interface StatusBarIconController { * Turns info from StatusBarIconController into ImageViews in a ViewGroup. */ class IconManager implements DemoModeCommandReceiver { - private final FeatureFlags mFeatureFlags; protected final ViewGroup mGroup; + private final FeatureFlags mFeatureFlags; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; + private final Provider<WifiViewModel> mWifiViewModelProvider; protected final Context mContext; protected final int mIconSize; // Whether or not these icons show up in dumpsys @@ -254,9 +299,15 @@ public interface StatusBarIconController { protected ArrayList<String> mBlockList = new ArrayList<>(); - public IconManager(ViewGroup group, FeatureFlags featureFlags) { - mFeatureFlags = featureFlags; + public IconManager( + ViewGroup group, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { mGroup = group; + mFeatureFlags = featureFlags; + mStatusBarPipelineFlags = statusBarPipelineFlags; + mWifiViewModelProvider = wifiViewModelProvider; mContext = group.getContext(); mIconSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); @@ -308,7 +359,7 @@ public interface StatusBarIconController { return addIcon(index, slot, blocked, holder.getIcon()); case TYPE_WIFI: - return addSignalIcon(index, slot, holder.getWifiState()); + return addWifiIcon(index, slot, holder.getWifiState()); case TYPE_MOBILE: return addMobileIcon(index, slot, holder.getMobileState()); @@ -327,9 +378,17 @@ public interface StatusBarIconController { } @VisibleForTesting - protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { - StatusBarWifiView view = onCreateStatusBarWifiView(slot); - view.applyWifiState(state); + protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) { + final BaseStatusBarWifiView view; + if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + view = onCreateModernStatusBarWifiView(slot); + // When [ModernStatusBarWifiView] is created, it will automatically apply the + // correct view state so we don't need to call applyWifiState. + } else { + StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot); + wifiView.applyWifiState(state); + view = wifiView; + } mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { @@ -359,6 +418,11 @@ public interface StatusBarIconController { return view; } + private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) { + return ModernStatusBarWifiView.constructAndBind( + mContext, slot, mWifiViewModelProvider.get()); + } + private StatusBarMobileView onCreateStatusBarMobileView(String slot) { StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot); return view; @@ -415,9 +479,8 @@ public interface StatusBarIconController { onSetIcon(viewIndex, holder.getIcon()); return; case TYPE_WIFI: - onSetSignalIcon(viewIndex, holder.getWifiState()); + onSetWifiIcon(viewIndex, holder.getWifiState()); return; - case TYPE_MOBILE: onSetMobileIcon(viewIndex, holder.getMobileState()); default: @@ -425,10 +488,16 @@ public interface StatusBarIconController { } } - public void onSetSignalIcon(int viewIndex, WifiIconState state) { - StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex); - if (wifiView != null) { - wifiView.applyWifiState(state); + public void onSetWifiIcon(int viewIndex, WifiIconState state) { + View view = mGroup.getChildAt(viewIndex); + if (view instanceof StatusBarWifiView) { + ((StatusBarWifiView) view).applyWifiState(state); + } else if (view instanceof ModernStatusBarWifiView) { + // ModernStatusBarWifiView will automatically apply state based on its callbacks, so + // we don't need to call applyWifiState. + } else { + throw new IllegalStateException("View at " + viewIndex + " must be of type " + + "StatusBarWifiView or ModernStatusBarWifiView"); } if (mIsInDemoMode) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 374f0916fb33..5cd2ba1b1cf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -223,12 +223,12 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble; final boolean willLaunchResolverActivity = isActivityIntent - && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), + && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent, mLockscreenUserManager.getCurrentUserId()); final boolean animate = !willLaunchResolverActivity && mCentralSurfaces.shouldAnimateLaunch(isActivityIntent); boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null - && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), + && mActivityIntentHelper.wouldPendingShowOverLockscreen(intent, mLockscreenUserManager.getCurrentUserId()); ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 40b9a152057a..70af77e1eb36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -259,8 +259,9 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, final boolean isActivity = pendingIntent.isActivity(); if (isActivity || appRequestedAuth) { mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent); - final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity( - pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId()); + final boolean afterKeyguardGone = mActivityIntentHelper + .wouldPendingLaunchResolverActivity(pendingIntent, + mLockscreenUserManager.getCurrentUserId()); mActivityStarter.dismissKeyguardThenExecute(() -> { mActionClickLogger.logKeyguardGone(pendingIntent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 200f45f7f8a3..fb5b0960bb86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -286,6 +286,7 @@ public abstract class StatusBarViewModule { PanelExpansionStateManager panelExpansionStateManager, FeatureFlags featureFlags, StatusBarIconController statusBarIconController, + StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NotificationPanelViewController notificationPanelViewController, @@ -306,6 +307,7 @@ public abstract class StatusBarViewModule { panelExpansionStateManager, featureFlags, statusBarIconController, + darkIconManagerFactory, statusBarHideIconsForBouncerManager, keyguardStateController, notificationPanelViewController, 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 f61b488a4fbb..5fd72b7a5247 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 @@ -124,6 +124,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final StatusBarIconController mStatusBarIconController; private final CarrierConfigTracker mCarrierConfigTracker; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; + private final StatusBarIconController.DarkIconManager.Factory mDarkIconManagerFactory; private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final DumpManager mDumpManager; @@ -172,6 +173,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue PanelExpansionStateManager panelExpansionStateManager, FeatureFlags featureFlags, StatusBarIconController statusBarIconController, + StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NotificationPanelViewController notificationPanelViewController, @@ -193,6 +195,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mFeatureFlags = featureFlags; mStatusBarIconController = statusBarIconController; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; + mDarkIconManagerFactory = darkIconManagerFactory; mKeyguardStateController = keyguardStateController; mNotificationPanelViewController = notificationPanelViewController; mStatusBarStateController = statusBarStateController; @@ -232,7 +235,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBar.restoreHierarchyState( savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE)); } - mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons), mFeatureFlags); + mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons)); mDarkIconManager.setShouldLog(true); updateBlockedIcons(); mStatusBarIconController.addIconGroup(mDarkIconManager); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt deleted file mode 100644 index 780a02da3410..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt +++ /dev/null @@ -1,49 +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.systemui.statusbar.pipeline - -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo -import kotlinx.coroutines.flow.StateFlow - -/** - * Interface exposing a flow for raw connectivity information. Clients should collect on - * [rawConnectivityInfoFlow] to get updates on connectivity information. - * - * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it - * and all clients get references to the same flow. - * - * This will be used for the new status bar pipeline to compile information we need to display some - * of the icons in the RHS of the status bar. - */ -interface ConnectivityInfoCollector { - val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> -} - -/** - * An object containing all of the raw connectivity information. - * - * Importantly, all the information in this object should not be processed at all (i.e., the data - * that we receive from callbacks should be piped straight into this object and not be filtered, - * manipulated, or processed in any way). Instead, any listeners on - * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing. - * - * This allows us to keep all the processing in one place which is beneficial for logging and - * debugging purposes. - */ -data class RawConnectivityInfo( - val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt deleted file mode 100644 index 99798f9b38d3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt +++ /dev/null @@ -1,46 +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.systemui.statusbar.pipeline - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** - * The real implementation of [ConnectivityInfoCollector] that will collect information from all the - * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow]. - */ -@SysUISingleton -class ConnectivityInfoCollectorImpl @Inject constructor( - networkCapabilitiesRepo: NetworkCapabilitiesRepo, - @Application scope: CoroutineScope, -) : ConnectivityInfoCollector { - override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> = - // TODO(b/238425913): Collect all the separate flows for individual raw information into - // this final flow. - networkCapabilitiesRepo.dataStream - .map { - RawConnectivityInfo(networkCapabilityInfo = it) - } - .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo()) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt index 64c47f679142..fe846747d0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt @@ -20,31 +20,21 @@ import android.content.Context import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** - * A processor that transforms raw connectivity information that we get from callbacks and turns it - * into a list of displayable connectivity information. + * A temporary object that collects on [WifiViewModel] flows for debugging purposes. * - * This will be used for the new status bar pipeline to calculate the list of icons that should be - * displayed in the RHS of the status bar. - * - * Anyone can listen to [processedInfoFlow] to get updates to the processed data. + * This will eventually get migrated to a view binder that will use the flow outputs to set state on + * views. For now, this just collects on flows so that the information gets logged. */ @SysUISingleton class ConnectivityInfoProcessor @Inject constructor( - connectivityInfoCollector: ConnectivityInfoCollector, context: Context, // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's // scope so we only do work when there's UI that cares about it. @@ -52,23 +42,8 @@ class ConnectivityInfoProcessor @Inject constructor( private val statusBarPipelineFlags: StatusBarPipelineFlags, private val wifiViewModelProvider: Provider<WifiViewModel>, ) : CoreStartable(context) { - // Note: This flow will not start running until a client calls `collect` on it, which means that - // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call - // happens. - // TODO(b/238425913): Delete this. - val processedInfoFlow: Flow<ProcessedConnectivityInfo> = - if (!statusBarPipelineFlags.isNewPipelineEnabled()) - emptyFlow() - else connectivityInfoCollector.rawConnectivityInfoFlow - .map { it.process() } - .stateIn( - scope, - started = Lazily, - initialValue = ProcessedConnectivityInfo() - ) - override fun start() { - if (!statusBarPipelineFlags.isNewPipelineEnabled()) { + if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) { return } // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can @@ -77,17 +52,4 @@ class ConnectivityInfoProcessor @Inject constructor( wifiViewModelProvider.get().isActivityInVisible.collect { } } } - - private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo { - // TODO(b/238425913): Actually process the raw info into meaningful data. - return ProcessedConnectivityInfo(this.networkCapabilityInfo) - } } - -/** - * An object containing connectivity info that has been processed into data that can be directly - * used by the status bar (and potentially other SysUI areas) to display icons. - */ -data class ProcessedConnectivityInfo( - val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 589cdb8182cb..9b8b6434827e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -25,13 +25,28 @@ import javax.inject.Inject @SysUISingleton class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** - * Returns true if we should run the new pipeline. + * Returns true if we should run the new pipeline backend. * - * TODO(b/238425913): We may want to split this out into: - * (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but - * doesn't change the UI at all. - * (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old - * pipeline doesn't change the UI). + * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs, + * and logs the output state. */ - fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE) + fun isNewPipelineBackendEnabled(): Boolean = + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND) + + /** + * Returns true if we should run the new pipeline frontend *and* backend. + * + * The new pipeline frontend will use the outputted state from the new backend and will make the + * correct changes to the UI. + */ + fun isNewPipelineFrontendEnabled(): Boolean = + isNewPipelineBackendEnabled() && + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND) + + /** + * Returns true if we should apply some coloring to icons that were rendered with the new + * pipeline to help with debugging. + */ + // For now, just always apply the debug coloring if we've enabled frontend rendering. + fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 7abe19e7bbe0..88eccb5ba47f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.pipeline.dagger import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector -import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl @@ -36,10 +34,5 @@ abstract class StatusBarPipelineModule { abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable @Binds - abstract fun provideConnectivityInfoCollector( - impl: ConnectivityInfoCollectorImpl - ): ConnectivityInfoCollector - - @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt index a5fff5e65ebc..2a89309f7200 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -31,6 +31,9 @@ import kotlinx.coroutines.flow.onEach class ConnectivityPipelineLogger @Inject constructor( @StatusBarConnectivityLog private val buffer: LogBuffer, ) { + /** + * Logs a change in one of the **raw inputs** to the connectivity pipeline. + */ fun logInputChange(callbackName: String, changeInfo: String) { buffer.log( SB_LOGGING_TAG, @@ -45,6 +48,41 @@ class ConnectivityPipelineLogger @Inject constructor( ) } + /** + * Logs a **data transformation** that we performed within the connectivity pipeline. + */ + fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) { + if (oldValue == newValue) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = transformationName + str2 = oldValue.toString() + }, + { + "Transform: $str1: $str2 (transformation didn't change it)" + } + ) + } else { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = transformationName + str2 = oldValue.toString() + str3 = newValue.toString() + }, + { + "Transform: $str1: $str2 -> $str3" + } + ) + } + } + + /** + * Logs a change in one of the **outputs** to the connectivity pipeline. + */ fun logOutputChange(outputParamName: String, changeInfo: String) { buffer.log( SB_LOGGING_TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt new file mode 100644 index 000000000000..5566fa6b5835 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -0,0 +1,46 @@ +/* + * 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.pipeline.wifi.data.model + +/** Provides information about the current wifi network. */ +sealed class WifiNetworkModel { + /** A model representing that we have no active wifi network. */ + object Inactive : WifiNetworkModel() + + /** Provides information about an active wifi network. */ + class Active( + /** + * The [android.net.Network.netId] we received from + * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. + * + * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. + */ + val networkId: Int, + + /** See [android.net.wifi.WifiInfo.ssid]. */ + val ssid: String? = null, + + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ + val isPasspointAccessPoint: Boolean = false, + + /** See [android.net.wifi.WifiInfo.isOsuAp]. */ + val isOnlineSignUpForPasspointAccessPoint: Boolean = false, + + /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ + val passpointProviderFriendlyName: String? = null, + ) : WifiNetworkModel() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt deleted file mode 100644 index 6c0a44524e3a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.statusbar.pipeline.wifi.data.repository - -import android.annotation.SuppressLint -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.stateIn - -/** - * Repository that contains all relevant [NetworkCapabilities] for the current networks. - * - * TODO(b/238425913): Figure out how to merge this with [WifiRepository]. - */ -@SysUISingleton -class NetworkCapabilitiesRepo @Inject constructor( - connectivityManager: ConnectivityManager, - @Application scope: CoroutineScope, - logger: ConnectivityPipelineLogger, -) { - @SuppressLint("MissingPermission") - val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run { - var state = emptyMap<Int, NetworkCapabilityInfo>() - callbackFlow { - val networkRequest: NetworkRequest = - NetworkRequest.Builder() - .clearCapabilities() - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .build() - val callback = - // TODO (b/240569788): log these using [LogBuffer] - object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - logger.logOnCapabilitiesChanged(network, networkCapabilities) - state = - state.toMutableMap().also { - it[network.getNetId()] = - NetworkCapabilityInfo(network, networkCapabilities) - } - trySend(state) - } - - override fun onLost(network: Network) { - logger.logOnLost(network) - state = state.toMutableMap().also { it.remove(network.getNetId()) } - trySend(state) - } - } - connectivityManager.registerNetworkCallback(networkRequest, callback) - - awaitClose { connectivityManager.unregisterNetworkCallback(callback) } - } - .stateIn(scope, started = Lazily, initialValue = state) - } -} - -/** contains info about network capabilities. */ -data class NetworkCapabilityInfo( - val network: Network, - val capabilities: NetworkCapabilities, -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 012dde5fb8f2..43fbabca823f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -16,16 +16,26 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository +import android.annotation.SuppressLint +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkRequest +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback import android.util.Log +import com.android.settingslib.Utils import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,9 +48,9 @@ import kotlinx.coroutines.flow.flowOf */ interface WifiRepository { /** - * Observable for the current state of wifi; `null` when there is no active wifi. + * Observable for the current wifi network. */ - val wifiModel: Flow<WifiModel?> + val wifiNetwork: Flow<WifiNetworkModel> /** * Observable for the current wifi network activity. @@ -51,14 +61,57 @@ interface WifiRepository { /** Real implementation of [WifiRepository]. */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton +@SuppressLint("MissingPermission") class WifiRepositoryImpl @Inject constructor( + connectivityManager: ConnectivityManager, wifiManager: WifiManager?, @Main mainExecutor: Executor, logger: ConnectivityPipelineLogger, ) : WifiRepository { + override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow { + var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT - // TODO(b/238425913): Actually implement the wifiModel flow. - override val wifiModel: Flow<WifiModel?> = flowOf(WifiModel(ssid = "AB")) + val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + logger.logOnCapabilitiesChanged(network, networkCapabilities) + + val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) + if (wifiInfo?.isPrimary == true) { + val wifiNetworkModel = wifiInfoToModel(wifiInfo, network.getNetId()) + logger.logTransformation( + WIFI_NETWORK_CALLBACK_NAME, + oldValue = currentWifi, + newValue = wifiNetworkModel + ) + currentWifi = wifiNetworkModel + trySend(wifiNetworkModel) + } + } + + override fun onLost(network: Network) { + logger.logOnLost(network) + val wifi = currentWifi + if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) { + val newNetworkModel = WifiNetworkModel.Inactive + logger.logTransformation( + WIFI_NETWORK_CALLBACK_NAME, + oldValue = wifi, + newValue = newNetworkModel + ) + currentWifi = newNetworkModel + trySend(newNetworkModel) + } + } + } + + trySend(WIFI_NETWORK_DEFAULT) + connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback) + + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } override val wifiActivity: Flow<WifiActivityModel> = if (wifiManager == null) { @@ -71,8 +124,8 @@ class WifiRepositoryImpl @Inject constructor( trySend(trafficStateToWifiActivityModel(state)) } - wifiManager.registerTrafficStateCallback(mainExecutor, callback) trySend(ACTIVITY_DEFAULT) + wifiManager.registerTrafficStateCallback(mainExecutor, callback) awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } } @@ -80,6 +133,13 @@ class WifiRepositoryImpl @Inject constructor( companion object { val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + // Start out with no known wifi network. + // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an + // initial fetch to get a starting wifi network. But, it uses a deprecated API + // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use + // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the + // NetworkCallback inside [wifiNetwork] for our wifi network information. + val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel { return WifiActivityModel( @@ -90,6 +150,30 @@ class WifiRepositoryImpl @Inject constructor( ) } + private fun networkCapabilitiesToWifiInfo( + networkCapabilities: NetworkCapabilities + ): WifiInfo? { + return when { + networkCapabilities.hasTransport(TRANSPORT_WIFI) -> + networkCapabilities.transportInfo as WifiInfo? + networkCapabilities.hasTransport(TRANSPORT_CELLULAR) -> + // Sometimes, cellular networks can act as wifi networks (known as VCN -- + // virtual carrier network). So, see if this cellular network has wifi info. + Utils.tryGetWifiInfoForVcn(networkCapabilities) + else -> null + } + } + + private fun wifiInfoToModel(wifiInfo: WifiInfo, networkId: Int): WifiNetworkModel { + return WifiNetworkModel.Active( + networkId, + wifiInfo.ssid, + wifiInfo.isPasspointAp, + wifiInfo.isOsuAp, + wifiInfo.passpointProviderFriendlyName + ) + } + private fun prettyPrintActivity(activity: Int): String { return when (activity) { TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" @@ -99,5 +183,15 @@ class WifiRepositoryImpl @Inject constructor( else -> "INVALID" } } + + private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest = + NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .build() + + private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index f705399af85d..a9da63b43aa4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor import android.net.wifi.WifiManager import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -34,13 +35,15 @@ import kotlinx.coroutines.flow.map class WifiInteractor @Inject constructor( repository: WifiRepository, ) { - private val ssid: Flow<String?> = repository.wifiModel.map { info -> - when { - info == null -> null - info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> - info.passpointProviderFriendlyName - info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid - else -> null + private val ssid: Flow<String?> = repository.wifiNetwork.map { info -> + when (info) { + is WifiNetworkModel.Inactive -> null + is WifiNetworkModel.Active -> when { + info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> + info.passpointProviderFriendlyName + info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + else -> null + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt new file mode 100644 index 000000000000..b06aaf441f5a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui.binder + +import android.content.res.ColorStateList +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** + * Binds a wifi icon in the status bar to its view-model. + * + * To use this properly, users should maintain a one-to-one relationship between the [View] and the + * view-binding, binding each view only once. It is okay and expected for the same instance of the + * view-model to be reused for multiple view/view-binder bindings. + */ +@OptIn(InternalCoroutinesApi::class) +object WifiViewBinder { + /** Binds the view to the view-model, continuing to update the former based on the latter. */ + @JvmStatic + fun bind( + view: ViewGroup, + viewModel: WifiViewModel, + ) { + val iconView = view.requireViewById<ImageView>(R.id.wifi_signal) + + view.isVisible = true + iconView.isVisible = true + iconView.setImageDrawable(view.context.getDrawable(WIFI_FULL_ICONS[2])) + + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.tint.collect { tint -> + iconView.imageTintList = ColorStateList.valueOf(tint) + } + } + } + } + + // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon. + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt new file mode 100644 index 000000000000..c14a897fffab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -0,0 +1,93 @@ +/* + * 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.pipeline.wifi.ui.view + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.LayoutInflater +import com.android.systemui.R +import com.android.systemui.statusbar.BaseStatusBarWifiView +import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel + +/** + * A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that + * is updated by [WifiViewBinder]. + */ +class ModernStatusBarWifiView( + context: Context, + attrs: AttributeSet? +) : BaseStatusBarWifiView(context, attrs) { + + private lateinit var slot: String + + override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { + // TODO(b/238425913) + } + + override fun getSlot() = slot + + override fun setStaticDrawableColor(color: Int) { + // TODO(b/238425913) + } + + override fun setDecorColor(color: Int) { + // TODO(b/238425913) + } + + override fun setVisibleState(state: Int, animate: Boolean) { + // TODO(b/238425913) + } + + override fun getVisibleState(): Int { + // TODO(b/238425913) + return STATE_ICON + } + + override fun isIconVisible(): Boolean { + // TODO(b/238425913) + return true + } + + /** Set the slot name for this view. */ + private fun setSlot(slotName: String) { + this.slot = slotName + } + + companion object { + /** + * Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and + * returns it. + */ + @JvmStatic + fun constructAndBind( + context: Context, + slot: String, + viewModel: WifiViewModel, + ): ModernStatusBarWifiView { + return ( + LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) + as ModernStatusBarWifiView + ).also { + it.setSlot(slot) + WifiViewBinder.bind(it, viewModel) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index b990eb7d9173..7a262605681d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel +import android.graphics.Color +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf /** @@ -30,9 +33,10 @@ import kotlinx.coroutines.flow.flowOf * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder. */ class WifiViewModel @Inject constructor( - private val constants: WifiConstants, - private val logger: ConnectivityPipelineLogger, - private val interactor: WifiInteractor, + statusBarPipelineFlags: StatusBarPipelineFlags, + private val constants: WifiConstants, + private val logger: ConnectivityPipelineLogger, + private val interactor: WifiInteractor, ) { val isActivityInVisible: Flow<Boolean> get() = @@ -42,4 +46,11 @@ class WifiViewModel @Inject constructor( interactor.hasActivityIn } .logOutputChange(logger, "activityInVisible") + + /** The tint that should be applied to the icon. */ + val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) { + emptyFlow() + } else { + flowOf(Color.CYAN) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index dfcdaefd8e03..836d57131fac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -45,7 +45,6 @@ import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyCallback; import android.text.TextUtils; -import android.util.FeatureFlagUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -287,10 +286,6 @@ public class UserSwitcherController implements Dumpable { refreshUsers(UserHandle.USER_NULL); } - private static boolean isEnableGuestModeUxChanges(Context context) { - return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES); - } - /** * Refreshes users from UserManager. * @@ -549,17 +544,9 @@ public class UserSwitcherController implements Dumpable { } if (currUserInfo != null && currUserInfo.isGuest()) { - if (isEnableGuestModeUxChanges(mContext)) { - showExitGuestDialog(currUserId, currUserInfo.isEphemeral(), - record.resolveId(), dialogShower); - return; - } else { - if (currUserInfo.isEphemeral()) { - showExitGuestDialog(currUserId, currUserInfo.isEphemeral(), - record.resolveId(), dialogShower); - return; - } - } + showExitGuestDialog(currUserId, currUserInfo.isEphemeral(), + record.resolveId(), dialogShower); + return; } if (dialogShower != null) { @@ -1056,14 +1043,8 @@ public class UserSwitcherController implements Dumpable { public String getName(Context context, UserRecord item) { if (item.isGuest) { if (item.isCurrent) { - if (isEnableGuestModeUxChanges(context)) { - return context.getString( - com.android.settingslib.R.string.guest_exit_quick_settings_button); - } else { - return context.getString(mController.mGuestUserAutoCreated - ? com.android.settingslib.R.string.guest_reset_guest - : com.android.settingslib.R.string.guest_exit_guest); - } + return context.getString( + com.android.settingslib.R.string.guest_exit_quick_settings_button); } else { if (item.info != null) { return context.getString(com.android.internal.R.string.guest_name); @@ -1080,13 +1061,8 @@ public class UserSwitcherController implements Dumpable { ? com.android.settingslib.R.string.guest_resetting : com.android.internal.R.string.guest_name); } else { - if (isEnableGuestModeUxChanges(context)) { - // we always show "guest" as string, instead of "add guest" - return context.getString(com.android.internal.R.string.guest_name); - } else { - return context.getString( - com.android.settingslib.R.string.guest_new_guest); - } + // we always show "guest" as string, instead of "add guest" + return context.getString(com.android.internal.R.string.guest_name); } } } @@ -1108,11 +1084,7 @@ public class UserSwitcherController implements Dumpable { protected static Drawable getIconDrawable(Context context, UserRecord item) { int iconRes; if (item.isAddUser) { - if (isEnableGuestModeUxChanges(context)) { - iconRes = R.drawable.ic_add; - } else { - iconRes = R.drawable.ic_account_circle_filled; - } + iconRes = R.drawable.ic_add; } else if (item.isGuest) { iconRes = R.drawable.ic_account_circle; } else if (item.isAddSupervisedUser) { @@ -1289,46 +1261,32 @@ public class UserSwitcherController implements Dumpable { ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral, int targetId) { super(context); - if (isEnableGuestModeUxChanges(context)) { - if (isGuestEphemeral) { - setTitle(context.getString( - com.android.settingslib.R.string.guest_exit_dialog_title)); - setMessage(context.getString( - com.android.settingslib.R.string.guest_exit_dialog_message)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString( - com.android.settingslib.R.string.guest_exit_dialog_button), this); - } else { - setTitle(context.getString( - com.android.settingslib - .R.string.guest_exit_dialog_title_non_ephemeral)); - setMessage(context.getString( - com.android.settingslib - .R.string.guest_exit_dialog_message_non_ephemeral)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_NEGATIVE, - context.getString( - com.android.settingslib.R.string.guest_exit_clear_data_button), - this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString( - com.android.settingslib.R.string.guest_exit_save_data_button), - this); - } + if (isGuestEphemeral) { + setTitle(context.getString( + com.android.settingslib.R.string.guest_exit_dialog_title)); + setMessage(context.getString( + com.android.settingslib.R.string.guest_exit_dialog_message)); + setButton(DialogInterface.BUTTON_NEUTRAL, + context.getString(android.R.string.cancel), this); + setButton(DialogInterface.BUTTON_POSITIVE, + context.getString( + com.android.settingslib.R.string.guest_exit_dialog_button), this); } else { - setTitle(mGuestUserAutoCreated - ? com.android.settingslib.R.string.guest_reset_guest_dialog_title - : com.android.settingslib.R.string.guest_remove_guest_dialog_title); - setMessage(context.getString(R.string.guest_exit_guest_dialog_message)); + setTitle(context.getString( + com.android.settingslib + .R.string.guest_exit_dialog_title_non_ephemeral)); + setMessage(context.getString( + com.android.settingslib + .R.string.guest_exit_dialog_message_non_ephemeral)); setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this); + setButton(DialogInterface.BUTTON_NEGATIVE, + context.getString( + com.android.settingslib.R.string.guest_exit_clear_data_button), + this); setButton(DialogInterface.BUTTON_POSITIVE, - context.getString(mGuestUserAutoCreated - ? com.android.settingslib.R.string.guest_reset_guest_confirm_button - : com.android.settingslib.R.string.guest_remove_guest_confirm_button), + context.getString( + com.android.settingslib.R.string.guest_exit_save_data_button), this); } SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing()); @@ -1345,39 +1303,29 @@ public class UserSwitcherController implements Dumpable { if (mFalsingManager.isFalseTap(penalty)) { return; } - if (isEnableGuestModeUxChanges(getContext())) { - if (mIsGuestEphemeral) { - if (which == DialogInterface.BUTTON_POSITIVE) { - mDialogLaunchAnimator.dismissStack(this); - // Ephemeral guest: exit guest, guest is removed by the system - // on exit, since its marked ephemeral - exitGuestUser(mGuestId, mTargetId, false); - } else if (which == DialogInterface.BUTTON_NEGATIVE) { - // Cancel clicked, do nothing - cancel(); - } - } else { - if (which == DialogInterface.BUTTON_POSITIVE) { - mDialogLaunchAnimator.dismissStack(this); - // Non-ephemeral guest: exit guest, guest is not removed by the system - // on exit, since its marked non-ephemeral - exitGuestUser(mGuestId, mTargetId, false); - } else if (which == DialogInterface.BUTTON_NEGATIVE) { - mDialogLaunchAnimator.dismissStack(this); - // Non-ephemeral guest: remove guest and then exit - exitGuestUser(mGuestId, mTargetId, true); - } else if (which == DialogInterface.BUTTON_NEUTRAL) { - // Cancel clicked, do nothing - cancel(); - } + if (mIsGuestEphemeral) { + if (which == DialogInterface.BUTTON_POSITIVE) { + mDialogLaunchAnimator.dismissStack(this); + // Ephemeral guest: exit guest, guest is removed by the system + // on exit, since its marked ephemeral + exitGuestUser(mGuestId, mTargetId, false); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + // Cancel clicked, do nothing + cancel(); } } else { - if (which == BUTTON_NEUTRAL) { - cancel(); - } else { - mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE); + if (which == DialogInterface.BUTTON_POSITIVE) { + mDialogLaunchAnimator.dismissStack(this); + // Non-ephemeral guest: exit guest, guest is not removed by the system + // on exit, since its marked non-ephemeral + exitGuestUser(mGuestId, mTargetId, false); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { mDialogLaunchAnimator.dismissStack(this); - removeGuestUser(mGuestId, mTargetId); + // Non-ephemeral guest: remove guest and then exit + exitGuestUser(mGuestId, mTargetId, true); + } else if (which == DialogInterface.BUTTON_NEUTRAL) { + // Cancel clicked, do nothing + cancel(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt new file mode 100644 index 000000000000..c0331e6000bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.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.util.kotlin + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.runBlocking + +/** + * A utility for handling incoming IPCs from a Binder interface in the order that they are received. + * + * This class serves as a replacement for the common [android.os.Handler] message-queue pattern, + * where IPCs can arrive on arbitrary threads and are all enqueued onto a queue and processed by the + * Handler in-order. + * + * class MyService : Service() { + * + * private val serializer = IpcSerializer() + * + * // Need to invoke process() in order to actually process IPCs sent over the serializer. + * override fun onStart(...) = lifecycleScope.launch { + * serializer.process() + * } + * + * // In your binder implementation, use runSerializedBlocking to enqueue a function onto + * // the serializer. + * override fun onBind(intent: Intent?) = object : IAidlService.Stub() { + * override fun ipcMethodFoo() = serializer.runSerializedBlocking { + * ... + * } + * + * override fun ipcMethodBar() = serializer.runSerializedBlocking { + * ... + * } + * } + * } + */ +class IpcSerializer { + + private val channel = Channel<Pair<CompletableDeferred<Unit>, Job>>() + + /** + * Runs functions enqueued via usage of [runSerialized] and [runSerializedBlocking] serially. + * This method will never complete normally, so it must be launched in its own coroutine; if + * this is not actively running, no enqueued functions will be evaluated. + */ + suspend fun process(): Nothing { + for ((start, finish) in channel) { + // Signal to the sender that serializer has reached this message + start.complete(Unit) + // Wait to hear from the sender that it has finished running it's work, before handling + // the next message + finish.join() + } + error("Unexpected end of serialization channel") + } + + /** + * Enqueues [block] for evaluation by the serializer, suspending the caller until it has + * completed. It is up to the caller to define what thread this is evaluated in, determined + * by the [kotlin.coroutines.CoroutineContext] used. + */ + suspend fun <R> runSerialized(block: suspend () -> R): R { + val start = CompletableDeferred(Unit) + val finish = CompletableDeferred(Unit) + // Enqueue our message on the channel. + channel.send(start to finish) + // Wait for the serializer to reach our message + start.await() + // Now evaluate the block + val result = block() + // Notify the serializer that we've completed evaluation + finish.complete(Unit) + return result + } + + /** + * Enqueues [block] for evaluation by the serializer, blocking the binder thread until it has + * completed. Evaluation occurs on the binder thread, so methods like + * [android.os.Binder.getCallingUid] that depend on the current thread will work as expected. + */ + fun <R> runSerializedBlocking(block: suspend () -> R): R = runBlocking { runSerialized(block) } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index aeab2dff9421..199048ec7b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -50,9 +50,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.system.QuickStepContract; @@ -77,7 +75,6 @@ import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleEntry; import com.android.wm.shell.bubbles.Bubbles; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -92,7 +89,7 @@ import java.util.function.IntConsumer; * The SysUi side bubbles manager which communicate with other SysUi components. */ @SysUISingleton -public class BubblesManager implements Dumpable { +public class BubblesManager { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesManager" : TAG_BUBBLES; @@ -137,7 +134,6 @@ public class BubblesManager implements Dumpable { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, - DumpManager dumpManager, Executor sysuiMainExecutor) { if (bubblesOptional.isPresent()) { return new BubblesManager(context, @@ -156,7 +152,6 @@ public class BubblesManager implements Dumpable { notifCollection, notifPipeline, sysUiState, - dumpManager, sysuiMainExecutor); } else { return null; @@ -180,7 +175,6 @@ public class BubblesManager implements Dumpable { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, - DumpManager dumpManager, Executor sysuiMainExecutor) { mContext = context; mBubbles = bubbles; @@ -203,8 +197,6 @@ public class BubblesManager implements Dumpable { setupNotifPipeline(); - dumpManager.registerDumpable(TAG, this); - keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardShowingChanged() { @@ -648,11 +640,6 @@ public class BubblesManager implements Dumpable { } } - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - mBubbles.dump(pw, args); - } - /** Checks whether bubbles are enabled for this user, handles negative userIds. */ public static boolean areBubblesEnabled(@NonNull Context context, @NonNull UserHandle user) { if (user.getIdentifier() < 0) { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index eba279587629..a4a59fc9d4a7 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -29,14 +29,16 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.view.KeyEvent; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; @@ -47,11 +49,11 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.wm.shell.nano.WmShellTraceProto; @@ -66,6 +68,7 @@ import com.android.wm.shell.sysui.ShellInterface; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; @@ -115,7 +118,7 @@ public final class WMShell extends CoreStartable private final SysUiState mSysUiState; private final WakefulnessLifecycle mWakefulnessLifecycle; private final ProtoTracer mProtoTracer; - private final UserInfoController mUserInfoController; + private final UserTracker mUserTracker; private final Executor mSysUiMainExecutor; // Listeners and callbacks. Note that we prefer member variable over anonymous class here to @@ -144,9 +147,20 @@ public final class WMShell extends CoreStartable mShell.onKeyguardDismissAnimationFinished(); } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mShell.onUserChanged(newUser, userContext); + } + + @Override + public void onProfilesChanged(@NonNull List<UserInfo> profiles) { + mShell.onUserProfilesChanged(profiles); + } + }; private boolean mIsSysUiStateValid; - private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback; private WakefulnessLifecycle.Observer mWakefulnessObserver; @Inject @@ -163,7 +177,7 @@ public final class WMShell extends CoreStartable SysUiState sysUiState, ProtoTracer protoTracer, WakefulnessLifecycle wakefulnessLifecycle, - UserInfoController userInfoController, + UserTracker userTracker, @Main Executor sysUiMainExecutor) { super(context); mShell = shell; @@ -178,7 +192,7 @@ public final class WMShell extends CoreStartable mOneHandedOptional = oneHandedOptional; mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; - mUserInfoController = userInfoController; + mUserTracker = userTracker; mSysUiMainExecutor = sysUiMainExecutor; } @@ -192,8 +206,9 @@ public final class WMShell extends CoreStartable mKeyguardStateController.addCallback(mKeyguardStateCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - // TODO: Consider piping config change and other common calls to a shell component to - // delegate internally + // Subscribe to user changes + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); + mProtoTracer.add(this); mCommandQueue.addCallback(this); mPipOptional.ifPresent(this::initPip); @@ -214,10 +229,6 @@ public final class WMShell extends CoreStartable mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0; pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag); }); - - // The media session listener needs to be re-registered when switching users - mUserInfoController.addCallback((String name, Drawable picture, String userAccount) -> - pip.registerSessionListenerForCurrentUser()); } @VisibleForTesting @@ -267,15 +278,6 @@ public final class WMShell extends CoreStartable } }); - // TODO: Either move into ShellInterface or register a receiver on the Shell side directly - mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onUserSwitchComplete(int userId) { - oneHanded.onUserSwitch(userId); - } - }; - mKeyguardUpdateMonitor.registerCallback(mOneHandedKeyguardCallback); - mWakefulnessObserver = new WakefulnessLifecycle.Observer() { @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index c281965550e4..3e00aec8cb28 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -1006,7 +1007,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN udfps is now enrolled when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); - callback.onEnrollmentsChanged(); + callback.onEnrollmentsChanged(TYPE_FINGERPRINT); // THEN isUdfspEnrolled is TRUE assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index d158892e4ec5..e0d1f7a19130 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -60,6 +60,9 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; +import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -154,7 +157,9 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private InteractionJankMonitor mInteractionJankMonitor; @Captor - ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor; + ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor; + @Captor + ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor; @Captor ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor; @Captor @@ -193,25 +198,38 @@ public class AuthControllerTest extends SysuiTestCase { when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point()); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - - final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal( - 1 /* sensorId */, - SensorProperties.STRENGTH_STRONG, - 1 /* maxEnrollmentsPerUser */, - componentInfo, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - true /* resetLockoutRequireHardwareAuthToken */); - List<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); - props.add(prop); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + final List<ComponentInfoInternal> fpComponentInfo = List.of( + new ComponentInfoInternal("faceSensor" /* componentId */, + "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, + "00000001" /* serialNumber */, "" /* softwareVersion */)); + final List<ComponentInfoInternal> faceComponentInfo = List.of( + new ComponentInfoInternal("matchingAlgorithm" /* componentId */, + "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, + "vendor/version/revision" /* softwareVersion */)); + + final List<FingerprintSensorPropertiesInternal> fpProps = List.of( + new FingerprintSensorPropertiesInternal( + 1 /* sensorId */, + SensorProperties.STRENGTH_STRONG, + 1 /* maxEnrollmentsPerUser */, + fpComponentInfo, + FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + true /* resetLockoutRequireHardwareAuthToken */)); + when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps); + + final List<FaceSensorPropertiesInternal> faceProps = List.of( + new FaceSensorPropertiesInternal( + 2 /* sensorId */, + SensorProperties.STRENGTH_STRONG, + 1 /* maxEnrollmentsPerUser */, + fpComponentInfo, + FaceSensorProperties.TYPE_RGB, + true /* supportsFaceDetection */, + true /* supportsSelfIllumination */, + true /* resetLockoutRequireHardwareAuthToken */)); + when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps); mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, @@ -219,12 +237,15 @@ public class AuthControllerTest extends SysuiTestCase { mAuthController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( - mAuthenticatorsRegisteredCaptor.capture()); + mFpAuthenticatorsRegisteredCaptor.capture()); + verify(mFaceManager).addAuthenticatorsRegisteredCallback( + mFaceAuthenticatorsRegisteredCaptor.capture()); when(mStatusBarStateController.isDozing()).thenReturn(false); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); - mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props); + mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps); + mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps); // Ensures that the operations posted on the handler get executed. mTestableLooper.processAllMessages(); @@ -237,6 +258,7 @@ public class AuthControllerTest extends SysuiTestCase { throws RemoteException { // This test is sensitive to prior FingerprintManager interactions. reset(mFingerprintManager); + reset(mFaceManager); // This test requires an uninitialized AuthController. AuthController authController = new TestableAuthController(mContextSpy, mExecution, @@ -246,21 +268,27 @@ public class AuthControllerTest extends SysuiTestCase { authController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( - mAuthenticatorsRegisteredCaptor.capture()); + mFpAuthenticatorsRegisteredCaptor.capture()); + verify(mFaceManager).addAuthenticatorsRegisteredCallback( + mFaceAuthenticatorsRegisteredCaptor.capture()); mTestableLooper.processAllMessages(); verify(mFingerprintManager, never()).registerBiometricStateListener(any()); + verify(mFaceManager, never()).registerBiometricStateListener(any()); - mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>()); + mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); + mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); mTestableLooper.processAllMessages(); verify(mFingerprintManager).registerBiometricStateListener(any()); + verify(mFaceManager).registerBiometricStateListener(any()); } @Test public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException { // This test is sensitive to prior FingerprintManager interactions. reset(mFingerprintManager); + reset(mFaceManager); // This test requires an uninitialized AuthController. AuthController authController = new TestableAuthController(mContextSpy, mExecution, @@ -270,18 +298,25 @@ public class AuthControllerTest extends SysuiTestCase { authController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( - mAuthenticatorsRegisteredCaptor.capture()); + mFpAuthenticatorsRegisteredCaptor.capture()); + verify(mFaceManager).addAuthenticatorsRegisteredCallback( + mFaceAuthenticatorsRegisteredCaptor.capture()); // Emulates a device with no authenticators (empty list). - mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>()); + mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); + mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); mTestableLooper.processAllMessages(); verify(mFingerprintManager).registerBiometricStateListener( mBiometricStateCaptor.capture()); + verify(mFaceManager).registerBiometricStateListener( + mBiometricStateCaptor.capture()); // Enrollments changed for an unknown sensor. - mBiometricStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */, - 0xbeef /* sensorId */, true /* hasEnrollments */); + for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) { + listener.onEnrollmentsChanged(0 /* userId */, + 0xbeef /* sensorId */, true /* hasEnrollments */); + } mTestableLooper.processAllMessages(); // Nothing should crash. @@ -827,4 +862,3 @@ public class AuthControllerTest extends SysuiTestCase { } } } - diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index 9ffc5a57cef6..b33f9a7f3933 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -16,6 +16,8 @@ package com.android.systemui.doze; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP; import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN; @@ -412,7 +414,7 @@ public class DozeSensorsTest extends SysuiTestCase { // WHEN enrollment changes to TRUE when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); - mAuthControllerCallback.onEnrollmentsChanged(); + mAuthControllerCallback.onEnrollmentsChanged(TYPE_FINGERPRINT); // THEN mConfigured = TRUE assertTrue(triggerSensor.mConfigured); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 01309f86a137..7f6b79b48939 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -308,6 +308,12 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test + public void testOnViewDetachedRemovesViews() { + mController.onViewDetached(); + verify(mView).removeAllStatusBarItemViews(); + } + + @Test public void testWifiIconHiddenWhenWifiBecomesAvailable() { // Make sure wifi starts out unavailable when onViewAttached is called, and then returns // true on the second query. diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java index 09976e0e6192..571dd3d1faf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java @@ -106,7 +106,7 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { private ContentObserver captureSettingsObserver() { verify(mSecureSettings).registerContentObserverForUser( - eq(Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS), + eq(Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED), mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId())); return mSettingsObserverCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index 24d051508fde..5ec6bdf3c00b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.keyguard; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; @@ -219,7 +221,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { Pair<Float, PointF> udfps = setupUdfps(); // WHEN all authenticators are registered - mAuthControllerCallback.onAllAuthenticatorsRegistered(); + mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); mDelayableExecutor.runAllReady(); // THEN lock icon view location is updated with the same coordinates as auth controller vals diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 19491f41a0c1..14b85b8b5e56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -37,6 +37,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlin.math.max +import kotlin.math.min import kotlin.reflect.KClass import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -127,6 +129,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, intent = Intent("action"), @@ -154,6 +157,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val config = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, intent = null, // This will cause it to tell the system that the click was handled. @@ -201,6 +205,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, intent = Intent("action"), @@ -260,6 +265,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = true, ) @@ -269,6 +275,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, ) @@ -342,6 +349,129 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun `isClickable - true when alpha at threshold`() = runBlockingTest { + repository.setKeyguardShowing(true) + repository.setBottomAreaAlpha( + KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + ) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = true, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + + @Test + fun `isClickable - true when alpha above threshold`() = runBlockingTest { + repository.setKeyguardShowing(true) + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + repository.setBottomAreaAlpha( + min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f), + ) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = true, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + + @Test + fun `isClickable - false when alpha below threshold`() = runBlockingTest { + repository.setKeyguardShowing(true) + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + repository.setBottomAreaAlpha( + max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f), + ) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = false, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + + @Test + fun `isClickable - false when alpha at zero`() = runBlockingTest { + repository.setKeyguardShowing(true) + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + repository.setBottomAreaAlpha(0f) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = false, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { repository.setDozeAmount(dozeAmount) return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET) @@ -384,6 +514,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ) { checkNotNull(viewModel) assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) + assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) if (testConfig.isVisible) { assertThat(viewModel.icon).isEqualTo(testConfig.icon) viewModel.onClicked.invoke( @@ -404,6 +535,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { private data class TestConfig( val isVisible: Boolean, + val isClickable: Boolean = false, val icon: ContainedDrawable? = null, val canShowWhileLocked: Boolean = false, val intent: Intent? = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt new file mode 100644 index 000000000000..373af5cdf4b7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt @@ -0,0 +1,57 @@ +/* + * 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.lifecycle + +import androidx.arch.core.executor.ArchTaskExecutor +import androidx.arch.core.executor.TaskExecutor +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +/** + * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert + * in LifecycleRegistry. + */ +class InstantTaskExecutorRule : TestWatcher() { + // TODO(b/240620122): This is a copy of + // androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced + // with a dependency on the real library once b/ is cleared. + override fun starting(description: Description) { + super.starting(description) + ArchTaskExecutor.getInstance() + .setDelegate( + object : TaskExecutor() { + override fun executeOnDiskIO(runnable: Runnable) { + runnable.run() + } + + override fun postToMainThread(runnable: Runnable) { + runnable.run() + } + + override fun isMainThread(): Boolean { + return true + } + } + ) + } + + override fun finished(description: Description) { + super.finished(description) + ArchTaskExecutor.getInstance().setDelegate(null) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt index 80f3e46b848f..91a6de6ae4c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt @@ -20,8 +20,6 @@ package com.android.systemui.lifecycle import android.testing.TestableLooper.RunWithLooper import android.view.View import android.view.ViewTreeObserver -import androidx.arch.core.executor.ArchTaskExecutor -import androidx.arch.core.executor.TaskExecutor import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest @@ -35,8 +33,6 @@ import kotlinx.coroutines.test.runBlockingTest import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.TestWatcher -import org.junit.runner.Description import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mock @@ -282,38 +278,4 @@ class RepeatWhenAttachedTest : SysuiTestCase() { _invocations.add(Invocation(lifecycleOwner)) } } - - /** - * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert - * in LifecycleRegistry. - */ - class InstantTaskExecutorRule : TestWatcher() { - // TODO(b/240620122): This is a copy of - // androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced - // with a dependency on the real library once b/ is cleared. - override fun starting(description: Description) { - super.starting(description) - ArchTaskExecutor.getInstance() - .setDelegate( - object : TaskExecutor() { - override fun executeOnDiskIO(runnable: Runnable) { - runnable.run() - } - - override fun postToMainThread(runnable: Runnable) { - runnable.run() - } - - override fun isMainThread(): Boolean { - return true - } - } - ) - } - - override fun finished(description: Description) { - super.finished(description) - ArchTaskExecutor.getInstance().setDelegate(null) - } - } } 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 260bb8760f1c..22ecb4b93743 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 @@ -78,7 +78,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices); when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false); - when(mMediaOutputController.isTransferring()).thenReturn(false); + when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat); when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat); when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1); @@ -208,7 +208,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() { - when(mMediaOutputController.isTransferring()).thenReturn(true); + when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -224,7 +224,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() { - when(mMediaOutputController.isTransferring()).thenReturn(true); + when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice2.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index be14cc51ef96..cb4f08e6c552 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -93,6 +93,10 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { private lateinit var featureFlags: FeatureFlags @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider + @Mock + private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory + @Mock + private lateinit var iconManager: StatusBarIconController.TintedIconManager private val qsExpansionPathInterpolator = QSExpansionPathInterpolator() @@ -106,6 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController) `when`(variableDateViewControllerFactory.create(any())) .thenReturn(variableDateViewController) + `when`(iconManagerFactory.create(any())).thenReturn(iconManager) `when`(view.resources).thenReturn(mContext.resources) `when`(view.isAttachedToWindow).thenReturn(true) `when`(view.context).thenReturn(context) @@ -122,7 +127,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { featureFlags, variableDateViewControllerFactory, batteryMeterViewController, - insetsProvider + insetsProvider, + iconManagerFactory, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java new file mode 100644 index 000000000000..d91baa5e7fcb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -0,0 +1,116 @@ +/* + * 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.qs.tiles; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.statusbar.connectivity.AccessPointController; +import com.android.systemui.statusbar.connectivity.NetworkController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class InternetTileTest extends SysuiTestCase { + + @Mock + private QSTileHost mHost; + @Mock + private NetworkController mNetworkController; + @Mock + private AccessPointController mAccessPointController; + @Mock + private InternetDialogFactory mInternetDialogFactory; + + private TestableLooper mTestableLooper; + private InternetTile mTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + when(mHost.getContext()).thenReturn(mContext); + when(mHost.getUserContext()).thenReturn(mContext); + + mTile = new InternetTile( + mHost, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mock(MetricsLogger.class), + mock(StatusBarStateController.class), + mock(ActivityStarter.class), + mock(QSLogger.class), + mNetworkController, + mAccessPointController, + mInternetDialogFactory + ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); + } + + @Test + public void setConnectivityStatus_defaultNetworkNotExists_updateTile() { + mTile.mSignalCallback.setConnectivityStatus( + /* noDefaultNetwork= */ true, + /* noValidatedNetwork= */ true, + /* noNetworksAvailable= */ true); + mTestableLooper.processAllMessages(); + assertThat(String.valueOf(mTile.getState().secondaryLabel)) + .isEqualTo(mContext.getString(R.string.quick_settings_networks_unavailable)); + assertThat(mTile.getLastTileState()).isEqualTo(1); + } + + @Test + public void setConnectivityStatus_defaultNetworkExists_notUpdateTile() { + mTile.mSignalCallback.setConnectivityStatus( + /* noDefaultNetwork= */ false, + /* noValidatedNetwork= */ true, + /* noNetworksAvailable= */ true); + mTestableLooper.processAllMessages(); + assertThat(String.valueOf(mTile.getState().secondaryLabel)) + .isNotEqualTo(mContext.getString(R.string.quick_settings_networks_unavailable)); + assertThat(String.valueOf(mTile.getState().secondaryLabel)) + .isNotEqualTo(mContext.getString(R.string.quick_settings_networks_available)); + assertThat(mTile.getLastTileState()).isEqualTo(-1); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index ed1a13b36d6c..20c6d9adc300 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -91,6 +91,10 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { @Mock private lateinit var statusBarIconController: StatusBarIconController @Mock + private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory + @Mock + private lateinit var iconManager: StatusBarIconController.TintedIconManager + @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder @@ -169,6 +173,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { } whenever(view.visibility).thenAnswer { _ -> viewVisibility } + whenever(iconManagerFactory.create(any())).thenReturn(iconManager) + whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true) whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true) @@ -178,6 +184,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { controller = LargeScreenShadeHeaderController( view, statusBarIconController, + iconManagerFactory, privacyIconsController, insetsProvider, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt index 02b26dbbc32d..eeb61bc8a0f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -43,6 +43,8 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var view: View @Mock private lateinit var statusIcons: StatusIconContainer @Mock private lateinit var statusBarIconController: StatusBarIconController + @Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory + @Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder @Mock private lateinit var featureFlags: FeatureFlags @@ -91,10 +93,12 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { whenever(view.visibility).thenAnswer { _ -> viewVisibility } whenever(variableDateViewControllerFactory.create(any())) .thenReturn(variableDateViewController) + whenever(iconManagerFactory.create(any())).thenReturn(iconManager) whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false) mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController( view, statusBarIconController, + iconManagerFactory, privacyIconsController, insetsProvider, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 95211c0c8ec8..32fa6e45bc37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -47,7 +47,6 @@ import android.content.ContentResolver; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.PointF; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; @@ -764,10 +763,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testSetDozing_notifiesNsslAndStateController() { - mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */, - null /* touch */); - verify(mNotificationStackScrollLayoutController) - .setDozing(eq(true), eq(false), eq(null)); + mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */); + verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false)); assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f); } @@ -1219,9 +1216,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); clearInvocations(mKeyguardStatusViewController); - mNotificationPanelViewController.setDozing(true, false, null); - - verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); + mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false); + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate= */ true); } @Test @@ -1233,7 +1229,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mMediaDataManager.hasActiveMedia()).thenReturn(true); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); - mNotificationPanelViewController.setDozing(true, false, null); + mNotificationPanelViewController.setDozing(true, false); verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false); } @@ -1547,8 +1543,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn); mNotificationPanelViewController.setDozing( /* dozing= */ dozing, - /* animate= */ false, - /* wakeUpTouchLocation= */ new PointF() + /* animate= */ false ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt new file mode 100644 index 000000000000..9393a4f4eead --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.rotation + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.Display +import android.view.WindowInsetsController +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper +class RotationButtonControllerTest : SysuiTestCase() { + + private lateinit var mController: RotationButtonController + + @Before + fun setUp() { + mController = RotationButtonController( + mContext, + /* lightIconColor = */ 0, + /* darkIconColor = */ 0, + /* iconCcwStart0ResId = */ 0, + /* iconCcwStart90ResId = */ 0, + /* iconCwStart0ResId = */ 0, + /* iconCwStart90ResId = */ 0 + ) { 0 } + } + + @Test + fun ifGestural_showRotationSuggestion() { + mController.onNavigationBarWindowVisibilityChange( /* showing = */ false) + mController.onBehaviorChanged(Display.DEFAULT_DISPLAY, + WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) + mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON) + mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false) + assertThat(mController.canShowRotationButton()).isFalse() + + mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) + + assertThat(mController.canShowRotationButton()).isTrue() + } + + @Test + fun ifTaskbarVisible_showRotationSuggestion() { + mController.onNavigationBarWindowVisibilityChange( /* showing = */ false) + mController.onBehaviorChanged(Display.DEFAULT_DISPLAY, + WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) + mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON) + mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false) + assertThat(mController.canShowRotationButton()).isFalse() + + mController.onTaskbarStateChange( /* visible = */ true, /* stashed = */ false) + + assertThat(mController.canShowRotationButton()).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index be631afdd1a9..1d8e5dec5c50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -38,8 +38,8 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -89,8 +89,8 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { val listener = mock(StatusBarStateController.StateListener::class.java) controller.addCallback(listener) - controller.setDozeAmount(0.5f, false /* animated */) - controller.setDozeAmount(0.5f, false /* animated */) + controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */) + controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */) verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat()) } @@ -135,7 +135,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { @Test fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() { // Put controller in AOD state - controller.setDozeAmount(1f, false) + controller.setAndInstrumentDozeAmount(null, 1f, false) // When waking from doze, CentralSurfaces#updateDozingState will update the dozing state // before the doze amount changes diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt index 214ba16dfc44..64d025628754 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt @@ -98,7 +98,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { @Test fun testPrepareDialogForApp_onlyDefaultChannel() { - group.channels = listOf(channelDefault) + group.addChannel(channelDefault) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, setOf(channelDefault), appIcon, clickListener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index f8b39e8cff71..137842ef314f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -28,7 +28,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -58,8 +57,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.FeedbackIcon; -import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import org.junit.Assert; @@ -260,17 +259,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void setNeedsRedactionFreesViewWhenFalse() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); - row.setNeedsRedaction(true); - row.getPublicLayout().setVisibility(View.GONE); - - row.setNeedsRedaction(false); - TestableLooper.get(this).processAllMessages(); - assertNull(row.getPublicLayout().getContractedChild()); - } - - @Test public void testAboveShelfChangedListenerCalled() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(); AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 11e502fc79bf..6cf1a12d94f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -47,7 +47,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.battery.BatteryMeterViewController; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -89,7 +88,9 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private StatusBarIconController mStatusBarIconController; @Mock - private FeatureFlags mFeatureFlags; + private StatusBarIconController.TintedIconManager.Factory mIconManagerFactory; + @Mock + private StatusBarIconController.TintedIconManager mIconManager; @Mock private BatteryMeterViewController mBatteryMeterViewController; @Mock @@ -129,6 +130,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); + when(mIconManagerFactory.create(any())).thenReturn(mIconManager); + allowTestableLooperAsMainThread(); TestableLooper.get(this).runWithLooper(() -> { mKeyguardStatusBarView = @@ -148,7 +151,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mBatteryController, mUserInfoController, mStatusBarIconController, - new StatusBarIconController.TintedIconManager.Factory(mFeatureFlags), + mIconManagerFactory, mBatteryMeterViewController, mNotificationPanelViewStateProvider, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java index 0f1c40bacb7b..a6b7e5103c78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java @@ -40,12 +40,16 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconMana import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import javax.inject.Provider; + @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -67,7 +71,11 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { @Test public void testSetCalledOnAdd_DarkIconManager() { LinearLayout layout = new LinearLayout(mContext); - TestDarkIconManager manager = new TestDarkIconManager(layout, mock(FeatureFlags.class)); + TestDarkIconManager manager = new TestDarkIconManager( + layout, + mock(FeatureFlags.class), + mock(StatusBarPipelineFlags.class), + () -> mock(WifiViewModel.class)); testCallOnAdd_forManager(manager); } @@ -104,8 +112,12 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { private static class TestDarkIconManager extends DarkIconManager implements TestableIconManager { - TestDarkIconManager(LinearLayout group, FeatureFlags featureFlags) { - super(group, featureFlags); + TestDarkIconManager( + LinearLayout group, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); } @Override @@ -123,7 +135,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { } @Override - protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) { StatusBarWifiView mock = mock(StatusBarWifiView.class); mGroup.addView(mock, index); return mock; @@ -140,7 +152,10 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { private static class TestIconManager extends IconManager implements TestableIconManager { TestIconManager(ViewGroup group) { - super(group, mock(FeatureFlags.class)); + super(group, + mock(FeatureFlags.class), + mock(StatusBarPipelineFlags.class), + () -> mock(WifiViewModel.class)); } @Override @@ -158,7 +173,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { } @Override - protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) { StatusBarWifiView mock = mock(StatusBarWifiView.class); mGroup.addView(mock, index); return mock; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index de43a1fabab6..2b805089a430 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -37,7 +37,6 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.Intent; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -135,8 +134,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private PendingIntent mContentIntent; @Mock - private Intent mContentIntentInner; - @Mock private OnUserInteractionCallback mOnUserInteractionCallback; @Mock private Runnable mFutureDismissalRunnable; @@ -159,7 +156,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mContentIntent.isActivity()).thenReturn(true); when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); - when(mContentIntent.getIntent()).thenReturn(mContentIntentInner); NotificationTestHelper notificationTestHelper = new NotificationTestHelper( mContext, @@ -374,7 +370,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { eq(entry.getKey()), any(NotificationVisibility.class)); // The content intent should NOT be sent on click. - verify(mContentIntent).getIntent(); verify(mContentIntent).isActivity(); verifyNoMoreInteractions(mContentIntent); } 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 4c8599d99ddd..ceaceb4695b9 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 @@ -111,6 +111,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock + private StatusBarIconController.DarkIconManager.Factory mIconManagerFactory; + @Mock + private StatusBarIconController.DarkIconManager mIconManager; + @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private DumpManager mDumpManager; @@ -424,6 +428,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class); when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); + when(mIconManagerFactory.create(any())).thenReturn(mIconManager); mSecureSettings = mock(SecureSettings.class); setUpNotificationIconAreaController(); @@ -436,6 +441,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { new PanelExpansionStateManager(), mock(FeatureFlags.class), mStatusBarIconController, + mIconManagerFactory, mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mNotificationPanelViewController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt deleted file mode 100644 index 7b492cb7ddd1..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt +++ /dev/null @@ -1,88 +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.systemui.statusbar.pipeline - -import android.net.NetworkCapabilities -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.`when` as whenever - -@OptIn(InternalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidTestingRunner::class) -class ConnectivityInfoProcessorTest : SysuiTestCase() { - - private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>() - - @Before - fun setUp() { - whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true) - } - - @Test - fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking { - // GIVEN a processor hooked up to a collector - val scope = CoroutineScope(Dispatchers.Unconfined) - val collector = FakeConnectivityInfoCollector() - val processor = ConnectivityInfoProcessor( - collector, - context, - scope, - statusBarPipelineFlags, - mock(), - ) - - var mostRecentValue: ProcessedConnectivityInfo? = null - val job = launch(start = CoroutineStart.UNDISPATCHED) { - processor.processedInfoFlow.collect { - mostRecentValue = it - } - } - - // WHEN the collector emits a value - val networkCapabilityInfo = mapOf( - 10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build()) - ) - collector.emitValue(RawConnectivityInfo(networkCapabilityInfo)) - // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as - // this test. So, our test needs to yield to let the job run. - // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this. - yield() - - // THEN the processor receives it - assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo) - - job.cancel() - scope.cancel() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index df389bc52664..6b8d4aa7c51f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -17,21 +17,22 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow /** Fake implementation of [WifiRepository] exposing set methods for all the flows. */ class FakeWifiRepository : WifiRepository { - private val _wifiModel: MutableStateFlow<WifiModel?> = MutableStateFlow(null) - override val wifiModel: Flow<WifiModel?> = _wifiModel + private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> = + MutableStateFlow(WifiNetworkModel.Inactive) + override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT) override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity - fun setWifiModel(wifiModel: WifiModel?) { - _wifiModel.value = wifiModel + fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) { + _wifiNetwork.value = wifiNetworkModel } fun setWifiActivity(activity: WifiActivityModel) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt deleted file mode 100644 index 6edf76ce77c9..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt +++ /dev/null @@ -1,252 +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.systemui.statusbar.pipeline.wifi.data.repository - -import android.net.ConnectivityManager -import android.net.ConnectivityManager.NetworkCallback -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED -import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.net.NetworkCapabilities.TRANSPORT_CELLULAR -import android.net.NetworkCapabilities.TRANSPORT_WIFI -import android.net.NetworkRequest -import android.test.suitebuilder.annotation.SmallTest -import android.testing.AndroidTestingRunner -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -// TODO(b/240619365): Update this test to use `runTest` when we update the testing library -@SmallTest -@RunWith(AndroidTestingRunner::class) -class NetworkCapabilitiesRepoTest : SysuiTestCase() { - @Mock private lateinit var connectivityManager: ConnectivityManager - @Mock private lateinit var logger: ConnectivityPipelineLogger - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - } - - @Test - fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN a new network is added - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - - val currentMap = repo.dataStream.value - - // THEN it is emitted from the flow - assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1) - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN two new networks are added - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_2, NET_2_CAPS) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects 2 networks - assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1) - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS) - assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2) - assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN a network is added, and then its capabilities are changed - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_1, NET_2_CAPS) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects the new capabilities - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnLost_networkIsRemoved() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN two new networks are added, and one is removed - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_2, NET_2_CAPS) - callback.onLost(NET_1) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects only the remaining network - assertThat(currentMap[NET_1_ID]).isNull() - assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2) - assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnLost_noNetworks_doesNotCrash() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN no networks are added, and one is removed - callback.onLost(NET_1) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow shows no networks - assertThat(currentMap).isEmpty() - - job.cancel() - scope.cancel() - } - - private val NET_1_ID = 100 - private val NET_1 = mock<Network>().also { - whenever(it.getNetId()).thenReturn(NET_1_ID) - } - private val NET_2_ID = 200 - private val NET_2 = mock<Network>().also { - whenever(it.getNetId()).thenReturn(NET_2_ID) - } - - private val NET_1_CAPS = NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_VALIDATED) - .build() - - private val NET_2_CAPS = NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_NOT_METERED) - .addCapability(NET_CAPABILITY_VALIDATED) - .build() -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt index 8b61364a2ac9..d0a38084af76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -16,16 +16,26 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.vcn.VcnTransportInfo +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor @@ -38,6 +48,7 @@ import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -47,6 +58,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: WifiRepositoryImpl @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var wifiManager: WifiManager private lateinit var executor: Executor @@ -54,11 +66,347 @@ class WifiRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) executor = FakeExecutor(FakeSystemClock()) + + underTest = WifiRepositoryImpl( + connectivityManager, + wifiManager, + executor, + logger, + ) + } + + @Test + fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT) + + job.cancel() + } + + @Test + fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(NETWORK_ID) + } + + getNetworkCallback().onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we update to a new primary network + val newNetworkId = 456 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + val newSsid = "CD" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } + + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(newWifiInfo) + ) + + // THEN we use the new network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(newNetworkId) + assertThat(latestActive.ssid).isEqualTo(newSsid) + + job.cancel() + } + + @Test + fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we notify of a new but non-primary network + val newNetworkId = 456 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + val newSsid = "EF" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(newWifiInfo) + ) + + // THEN we still use the original network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + // WHEN we keep the same network ID but change the SSID + val newSsid = "CD" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(newWifiInfo)) + + // THEN we've updated to the new SSID + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(newSsid) + + job.cancel() + } + + @Test + fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand + getNetworkCallback().onLost(NETWORK) + + // THEN there's no crash and we still have no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we lose our current network + getNetworkCallback().onLost(NETWORK) + + // THEN we update to no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we lose an unknown network + val unknownNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(543) + } + getNetworkCallback().onLost(unknownNetwork) + + // THEN we still have our previous network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we update to a new network... + val newNetworkId = 89 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) + ) + // ...and lose the old network + getNetworkCallback().onLost(NETWORK) + + // THEN we still have the new network + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId) + + job.cancel() } @Test fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) { underTest = WifiRepositoryImpl( + connectivityManager, wifiManager = null, executor, logger, @@ -77,12 +425,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -100,12 +442,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -123,12 +459,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -146,12 +476,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -170,6 +494,30 @@ class WifiRepositoryImplTest : SysuiTestCase() { verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) return callbackCaptor.value!! } + + private fun getNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun createWifiNetworkCapabilities(wifiInfo: WifiInfo) = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(this.transportInfo).thenReturn(wifiInfo) + } + + private companion object { + const val NETWORK_ID = 45 + val NETWORK = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(NETWORK_ID) + } + const val SSID = "AB" + val PRIMARY_WIFI_INFO: WifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + } } private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt index c52f347a605a..5f1b1dbb19dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers @@ -47,7 +47,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false)) var latest: Boolean? = null @@ -63,7 +63,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true)) var latest: Boolean? = null @@ -79,7 +79,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) var latest: Boolean? = null @@ -95,7 +95,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) var latest: Boolean? = null @@ -111,7 +111,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = null)) + repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null)) repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) var latest: Boolean? = null @@ -127,7 +127,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) var latest: Boolean? = null val job = underTest @@ -158,6 +158,10 @@ class WifiInteractorTest : SysuiTestCase() { job.cancel() } + + companion object { + val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB") + } } private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt new file mode 100644 index 000000000000..3c200a5da4fa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -0,0 +1,53 @@ +/* + * 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.pipeline.wifi.ui.view + +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.lifecycle.InstantTaskExecutorRule +import com.android.systemui.util.Assert +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +@RunWithLooper +class ModernStatusBarWifiViewTest : SysuiTestCase() { + + @JvmField @Rule + val instantTaskExecutor = InstantTaskExecutorRule() + + @Before + fun setUp() { + Assert.setTestThread(Thread.currentThread()) + } + + @Test + fun constructAndBind_hasCorrectSlot() { + val view = ModernStatusBarWifiView.constructAndBind( + context, "slotName", mock() + ) + + assertThat(view.slot).isEqualTo("slotName") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index e9259b071e0b..c79073409883 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -18,9 +18,10 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants @@ -43,6 +44,7 @@ class WifiViewModelTest : SysuiTestCase() { private lateinit var underTest: WifiViewModel + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var constants: WifiConstants private lateinit var repository: FakeWifiRepository @@ -55,13 +57,14 @@ class WifiViewModelTest : SysuiTestCase() { interactor = WifiInteractor(repository) underTest = WifiViewModel( - constants, - logger, - interactor + statusBarPipelineFlags, + constants, + logger, + interactor ) // Set up with a valid SSID - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = "AB")) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt new file mode 100644 index 000000000000..15ba67205034 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt @@ -0,0 +1,71 @@ +/* + * 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.util.kotlin + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import java.util.concurrent.atomic.AtomicLong +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IpcSerializerTest : SysuiTestCase() { + + private val serializer = IpcSerializer() + + @Test + fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) { + val processor = launch(start = CoroutineStart.LAZY) { serializer.process() } + withContext(Dispatchers.IO) { + val lastEvaluatedTime = AtomicLong(System.currentTimeMillis()) + // First, launch many serialization requests in parallel + repeat(100_000) { + launch(Dispatchers.Unconfined) { + val enqueuedTime = System.currentTimeMillis() + serializer.runSerialized { + val last = lastEvaluatedTime.getAndSet(enqueuedTime) + assertTrue( + "expected $last less than or equal to $enqueuedTime ", + last <= enqueuedTime, + ) + } + } + } + // Then, process them all in the order they came in. + processor.start() + } + // All done, stop processing + processor.cancel() + } + + @Test(timeout = 5000) + fun serializeOnOneThread_doesNotDeadlock() = runBlocking { + val job = launch { serializer.process() } + repeat(100) { + serializer.runSerializedBlocking { } + } + job.cancel() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 8f2b715ba051..5d63632725c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -135,6 +135,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -225,6 +226,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private ShellInit mShellInit; @Mock + private ShellCommandHandler mShellCommandHandler; + @Mock private ShellController mShellController; @Mock private Bubbles.BubbleExpandListener mBubbleExpandListener; @@ -349,6 +352,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleController = new TestableBubbleController( mContext, mShellInit, + mShellCommandHandler, mShellController, mBubbleData, mFloatingContentCoordinator, @@ -389,7 +393,6 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, - mDumpManager, syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); @@ -1395,6 +1398,33 @@ public class BubblesTest extends SysuiTestCase { assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); } + /** + * Test to verify behavior for following situation: + * <ul> + * <li>status bar shade state is set to <code>false</code></li> + * <li>there is a bubble pending to be expanded</li> + * </ul> + * Test that duplicate status bar state updates to <code>false</code> do not clear the + * pending bubble to be + * expanded. + */ + @Test + public void testOnStatusBarStateChanged_statusBarChangeDoesNotClearExpandingBubble() { + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.onStatusBarStateChanged(false); + // Set the bubble to expand once status bar state changes + mBubbleController.expandStackAndSelectBubble(mBubbleEntry); + // Check that stack is currently collapsed + assertStackCollapsed(); + // Post status bar state change update with the same value + mBubbleController.onStatusBarStateChanged(false); + // Stack should remain collapsedb + assertStackCollapsed(); + // Post status bar state change which should trigger bubble to expand + mBubbleController.onStatusBarStateChanged(true); + assertStackExpanded(); + } + @Test public void testSetShouldAutoExpand_notifiesFlagChanged() { mBubbleController.updateBubble(mBubbleEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 880ad187f910..6357a09eb196 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -38,6 +38,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -51,6 +52,7 @@ public class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, FloatingContentCoordinator floatingContentCoordinator, @@ -71,12 +73,12 @@ public class TestableBubbleController extends BubbleController { Handler shellMainHandler, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { - super(context, shellInit, shellController, data, Runnable::run, floatingContentCoordinator, - dataRepository, statusBarService, windowManager, windowManagerShellWrapper, - userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, - positioner, displayController, oneHandedOptional, dragAndDropController, - shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions, - syncQueue); + super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run, + floatingContentCoordinator, dataRepository, statusBarService, windowManager, + windowManagerShellWrapper, userManager, launcherApps, bubbleLogger, + taskStackListener, shellTaskOrganizer, positioner, displayController, + oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler, + new SyncExecutor(), taskViewTransitions, syncQueue); setInflateSynchronously(true); onInit(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 9c2136675dfa..da33fa62a9ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -28,10 +28,10 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.onehanded.OneHanded; @@ -72,7 +72,7 @@ public class WMShellTest extends SysuiTestCase { @Mock OneHanded mOneHanded; @Mock WakefulnessLifecycle mWakefulnessLifecycle; @Mock ProtoTracer mProtoTracer; - @Mock UserInfoController mUserInfoController; + @Mock UserTracker mUserTracker; @Mock ShellExecutor mSysUiMainExecutor; @Before @@ -83,7 +83,7 @@ public class WMShellTest extends SysuiTestCase { Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue, mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, - mUserInfoController, mSysUiMainExecutor); + mUserTracker, mSysUiMainExecutor); } @Test diff --git a/services/OWNERS b/services/OWNERS index 67cee5517913..495c0737e599 100644 --- a/services/OWNERS +++ b/services/OWNERS @@ -1,4 +1,4 @@ -per-file Android.bp = file:platform/build/soong:/OWNERS +per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} # art-team@ manages the system server profile per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com diff --git a/services/api/OWNERS b/services/api/OWNERS index a6093900c635..e10440c1aed5 100644 --- a/services/api/OWNERS +++ b/services/api/OWNERS @@ -1,4 +1,4 @@ -per-file Android.bp = file:platform/build/soong:/OWNERS +per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} # API changes are managed via Prolog rules, not OWNERS * diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 0b858cf38d44..eb1d2d762776 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2987,8 +2987,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mBarringInfo=" + mBarringInfo.get(i)); pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState[i]); pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]); - pw.println("mIsDataEnabled=" + mIsDataEnabled); - pw.println("mDataEnabledReason=" + mDataEnabledReason); + pw.println("mIsDataEnabled=" + mIsDataEnabled[i]); + pw.println("mDataEnabledReason=" + mDataEnabledReason[i]); pw.println("mAllowedNetworkTypeReason=" + mAllowedNetworkTypeReason[i]); pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]); pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i)); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 297e6a2c5fc7..734f45523618 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -454,6 +454,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; @@ -5501,7 +5502,7 @@ public class ActivityManagerService extends IActivityManager.Stub IIntentSender pendingResult, int matchFlags) { enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT, "queryIntentComponentsForIntentSender()"); - Preconditions.checkNotNull(pendingResult); + Objects.requireNonNull(pendingResult); final PendingIntentRecord res; try { res = (PendingIntentRecord) pendingResult; @@ -5513,17 +5514,19 @@ public class ActivityManagerService extends IActivityManager.Stub return null; } final int userId = res.key.userId; + final int uid = res.uid; + final String resolvedType = res.key.requestResolvedType; switch (res.key.type) { case ActivityManager.INTENT_SENDER_ACTIVITY: - return new ParceledListSlice<>(mContext.getPackageManager() - .queryIntentActivitiesAsUser(intent, matchFlags, userId)); + return new ParceledListSlice<>(mPackageManagerInt.queryIntentActivities( + intent, resolvedType, matchFlags, uid, userId)); case ActivityManager.INTENT_SENDER_SERVICE: case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE: - return new ParceledListSlice<>(mContext.getPackageManager() - .queryIntentServicesAsUser(intent, matchFlags, userId)); + return new ParceledListSlice<>(mPackageManagerInt.queryIntentServices( + intent, matchFlags, uid, userId)); case ActivityManager.INTENT_SENDER_BROADCAST: - return new ParceledListSlice<>(mContext.getPackageManager() - .queryBroadcastReceiversAsUser(intent, matchFlags, userId)); + return new ParceledListSlice<>(mPackageManagerInt.queryIntentReceivers( + intent, resolvedType, matchFlags, uid, userId, false)); default: // ActivityManager.INTENT_SENDER_ACTIVITY_RESULT throw new IllegalStateException("Unsupported intent sender type: " + res.key.type); } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 1302e226eba3..134e2061c090 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -502,6 +502,14 @@ public final class GameManagerService extends IGameManagerService.Stub { GamePackageConfiguration(PackageManager packageManager, String packageName, int userId) { mPackageName = packageName; + + // set flag default values + mPerfModeOptedIn = false; + mBatteryModeOptedIn = false; + mAllowDownscale = true; + mAllowAngle = true; + mAllowFpsOverride = true; + try { final ApplicationInfo ai = packageManager.getApplicationInfoAsUser(packageName, PackageManager.GET_META_DATA, userId); @@ -511,12 +519,6 @@ public final class GameManagerService extends IGameManagerService.Stub { mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE); mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true); mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true); - } else { - mPerfModeOptedIn = false; - mBatteryModeOptedIn = false; - mAllowDownscale = true; - mAllowAngle = true; - mAllowFpsOverride = true; } } } catch (NameNotFoundException e) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a5bcb0517d25..248e35e6964d 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -63,7 +63,6 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_PERSISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; -import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; @@ -563,6 +562,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public ArrayMap<String, Ops> pkgOps; // true indicates there is an interested observer, false there isn't but it has such an op + //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface. public SparseBooleanArray foregroundOps; public boolean hasForegroundWatchers; @@ -658,48 +658,24 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mode; } - private void evalForegroundWatchers(int op, SparseArray<ArraySet<ModeCallback>> watchers, - SparseBooleanArray which) { - boolean curValue = which.get(op, false); - ArraySet<ModeCallback> callbacks = watchers.get(op); - if (callbacks != null) { - for (int cbi = callbacks.size() - 1; !curValue && cbi >= 0; cbi--) { - if ((callbacks.valueAt(cbi).mFlags - & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) { - hasForegroundWatchers = true; - curValue = true; - } + public void evalForegroundOps() { + foregroundOps = null; + foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps); + if (pkgOps != null) { + for (int i = pkgOps.size() - 1; i >= 0; i--) { + foregroundOps = mAppOpsServiceInterface + .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps); } } - which.put(op, curValue); - } - - public void evalForegroundOps(SparseArray<ArraySet<ModeCallback>> watchers) { - SparseBooleanArray which = null; hasForegroundWatchers = false; - final SparseIntArray opModes = getNonDefaultUidModes(); - for (int i = opModes.size() - 1; i >= 0; i--) { - if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) { - if (which == null) { - which = new SparseBooleanArray(); - } - evalForegroundWatchers(opModes.keyAt(i), watchers, which); - } - } - if (pkgOps != null) { - for (int i = pkgOps.size() - 1; i >= 0; i--) { - Ops ops = pkgOps.valueAt(i); - for (int j = ops.size() - 1; j >= 0; j--) { - if (ops.valueAt(j).getMode() == AppOpsManager.MODE_FOREGROUND) { - if (which == null) { - which = new SparseBooleanArray(); - } - evalForegroundWatchers(ops.keyAt(j), watchers, which); - } + if (foregroundOps != null) { + for (int i = 0; i < foregroundOps.size(); i++) { + if (foregroundOps.valueAt(i)) { + hasForegroundWatchers = true; + break; } } } - foregroundOps = which; } } @@ -1562,33 +1538,24 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - final SparseArray<ArraySet<ModeCallback>> mOpModeWatchers = new SparseArray<>(); - final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>(); final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>(); final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>(); final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>(); final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>(); final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager(); - final class ModeCallback implements DeathRecipient { + final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient { /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ public static final int ALL_OPS = -2; - final IAppOpsCallback mCallback; - final int mWatchingUid; - final int mFlags; - final int mWatchedOpCode; - final int mCallingUid; - final int mCallingPid; + // Need to keep this only because stopWatchingMode needs an IAppOpsCallback. + // Otherwise we can just use the IBinder object. + private final IAppOpsCallback mCallback; - ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp, + ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode, int callingUid, int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mFlags = flags; - mWatchedOpCode = watchedOp; - mCallingUid = callingUid; - mCallingPid = callingPid; + super(watchingUid, flags, watchedOpCode, callingUid, callingPid); + this.mCallback = callback; try { mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { @@ -1596,20 +1563,16 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - public boolean isWatchingUid(int uid) { - return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("ModeCallback{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); + UserHandle.formatUid(sb, getWatchingUid()); sb.append(" flags=0x"); - sb.append(Integer.toHexString(mFlags)); - switch (mWatchedOpCode) { + sb.append(Integer.toHexString(getFlags())); + switch (getWatchedOpCode()) { case OP_NONE: break; case ALL_OPS: @@ -1617,13 +1580,13 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch break; default: sb.append(" op="); - sb.append(opToName(mWatchedOpCode)); + sb.append(opToName(getWatchedOpCode())); break; } sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); + UserHandle.formatUid(sb, getCallingUid()); sb.append(" pid="); - sb.append(mCallingPid); + sb.append(getCallingPid()); sb.append('}'); return sb.toString(); } @@ -1636,6 +1599,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public void binderDied() { stopWatchingMode(mCallback); } + + @Override + public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException { + mCallback.opChanged(op, uid, packageName); + } } final class ActiveCallback implements DeathRecipient { @@ -1804,7 +1772,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public AppOpsService(File storagePath, Handler handler, Context context) { mContext = context; - mAppOpsServiceInterface = new LegacyAppOpsServiceInterfaceImpl(this, this); + + for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { + int switchCode = AppOpsManager.opToSwitch(switchedCode); + mSwitchedOps.put(switchCode, + ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); + } + mAppOpsServiceInterface = + new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps); LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mFile = new AtomicFile(storagePath, "appops"); @@ -1818,12 +1793,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mHandler = handler; mConstants = new Constants(mHandler); readState(); - - for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { - int switchCode = AppOpsManager.opToSwitch(switchedCode); - mSwitchedOps.put(switchCode, - ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); - } } public void publish() { @@ -1982,20 +1951,20 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final String[] changedPkgs = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_PACKAGE_LIST); for (int code : OPS_RESTRICTED_ON_SUSPEND) { - ArraySet<ModeCallback> callbacks; + ArraySet<OnOpModeChangedListener> onModeChangedListeners; synchronized (AppOpsService.this) { - callbacks = mOpModeWatchers.get(code); - if (callbacks == null) { + onModeChangedListeners = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (onModeChangedListeners == null) { continue; } - callbacks = new ArraySet<>(callbacks); } for (int i = 0; i < changedUids.length; i++) { final int changedUid = changedUids[i]; final String changedPkg = changedPkgs[i]; // We trust packagemanager to insert matching uid and packageNames in the // extras - notifyOpChanged(callbacks, code, changedUid, changedPkg); + notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg); } } } @@ -2596,7 +2565,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (!uidState.setUidMode(code, mode)) { return; } - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); if (mode != MODE_ERRORED && mode != previousMode) { updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); } @@ -2615,78 +2584,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, @Nullable IAppOpsCallback callbackToIgnore) { - String[] uidPackageNames = getPackagesForUid(uid); - ArrayMap<ModeCallback, ArraySet<String>> callbackSpecs = null; - - synchronized (this) { - ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); - if (callbacks != null) { - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - ModeCallback callback = callbacks.valueAt(i); - if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) { - continue; - } - - ArraySet<String> changedPackages = new ArraySet<>(); - Collections.addAll(changedPackages, uidPackageNames); - if (callbackSpecs == null) { - callbackSpecs = new ArrayMap<>(); - } - callbackSpecs.put(callback, changedPackages); - } - } - - for (String uidPackageName : uidPackageNames) { - callbacks = mPackageModeWatchers.get(uidPackageName); - if (callbacks != null) { - if (callbackSpecs == null) { - callbackSpecs = new ArrayMap<>(); - } - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - ModeCallback callback = callbacks.valueAt(i); - if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) { - continue; - } - - ArraySet<String> changedPackages = callbackSpecs.get(callback); - if (changedPackages == null) { - changedPackages = new ArraySet<>(); - callbackSpecs.put(callback, changedPackages); - } - changedPackages.add(uidPackageName); - } - } - } - - if (callbackSpecs != null && callbackToIgnore != null) { - callbackSpecs.remove(mModeWatchers.get(callbackToIgnore.asBinder())); - } - } - - if (callbackSpecs == null) { - return; - } - - for (int i = 0; i < callbackSpecs.size(); i++) { - final ModeCallback callback = callbackSpecs.keyAt(i); - final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i); - if (reportedPackageNames == null) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, callback, code, uid, (String) null)); - - } else { - final int reportedPackageCount = reportedPackageNames.size(); - for (int j = 0; j < reportedPackageCount; j++) { - final String reportedPackageName = reportedPackageNames.valueAt(j); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, callback, code, uid, reportedPackageName)); - } - } - } + ModeCallback listenerToIgnore = callbackToIgnore != null + ? mModeWatchers.get(callbackToIgnore.asBinder()) : null; + mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground, + listenerToIgnore); } private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { @@ -2810,7 +2711,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return; } - ArraySet<ModeCallback> repCbs = null; + ArraySet<OnOpModeChangedListener> repCbs = null; code = AppOpsManager.opToSwitch(code); PackageVerificationResult pvr; @@ -2831,16 +2732,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch op.setMode(mode); if (uidState != null) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } - ArraySet<ModeCallback> cbs = mOpModeWatchers.get(code); + ArraySet<OnOpModeChangedListener> cbs = + mAppOpsServiceInterface.getOpModeChangedListeners(code); if (cbs != null) { if (repCbs == null) { repCbs = new ArraySet<>(); } repCbs.addAll(cbs); } - cbs = mPackageModeWatchers.get(packageName); + cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName); if (cbs != null) { if (repCbs == null) { repCbs = new ArraySet<>(); @@ -2871,47 +2773,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch notifyOpChangedSync(code, uid, packageName, mode, previousMode); } - private void notifyOpChanged(ArraySet<ModeCallback> callbacks, int code, + private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code, int uid, String packageName) { for (int i = 0; i < callbacks.size(); i++) { - final ModeCallback callback = callbacks.valueAt(i); + final OnOpModeChangedListener callback = callbacks.valueAt(i); notifyOpChanged(callback, code, uid, packageName); } } - private void notifyOpChanged(ModeCallback callback, int code, + private void notifyOpChanged(OnOpModeChangedListener callback, int code, int uid, String packageName) { - if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - return; - } - - // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE - int[] switchedCodes; - if (callback.mWatchedOpCode == ALL_OPS) { - switchedCodes = mSwitchedOps.get(code); - } else if (callback.mWatchedOpCode == OP_NONE) { - switchedCodes = new int[]{code}; - } else { - switchedCodes = new int[]{callback.mWatchedOpCode}; - } - - for (int switchedCode : switchedCodes) { - // There are features watching for mode changes such as window manager - // and location manager which are in our process. The callbacks in these - // features may require permissions our remote caller does not have. - final long identity = Binder.clearCallingIdentity(); - try { - if (shouldIgnoreCallback(switchedCode, callback.mCallingPid, - callback.mCallingUid)) { - continue; - } - callback.mCallback.opChanged(switchedCode, uid, packageName); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(identity); - } - } + mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName); } private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports, @@ -2936,9 +2808,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return reports; } - private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks( - HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks, - int op, int uid, String packageName, int previousMode, ArraySet<ModeCallback> cbs) { + private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks( + HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks, + int op, int uid, String packageName, int previousMode, + ArraySet<OnOpModeChangedListener> cbs) { if (cbs == null) { return callbacks; } @@ -2947,7 +2820,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } final int N = cbs.size(); for (int i=0; i<N; i++) { - ModeCallback cb = cbs.valueAt(i); + OnOpModeChangedListener cb = cbs.valueAt(i); ArrayList<ChangeRec> reports = callbacks.get(cb); ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode); if (changed != reports) { @@ -2990,7 +2863,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch enforceManageAppOpsModes(callingPid, callingUid, reqUid); - HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null; + HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null; ArrayList<ChangeRec> allChanges = new ArrayList<>(); synchronized (this) { boolean changed = false; @@ -3007,9 +2880,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code)); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mOpModeWatchers.get(code)); + previousMode, + mAppOpsServiceInterface.getOpModeChangedListeners(code)); callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mPackageModeWatchers.get(packageName)); + previousMode, mAppOpsServiceInterface + .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, code, uidState.uid, packageName, previousMode); @@ -3053,9 +2928,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch uidChanged = true; final int uid = curOp.uidState.uid; callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mOpModeWatchers.get(curOp.op)); + previousMode, + mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op)); callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mPackageModeWatchers.get(packageName)); + previousMode, mAppOpsServiceInterface + .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, curOp.op, uid, packageName, previousMode); @@ -3075,7 +2952,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mUidStates.remove(uidState.uid); } if (uidChanged) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } } @@ -3084,8 +2961,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } if (callbacks != null) { - for (Map.Entry<ModeCallback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) { - ModeCallback cb = ent.getKey(); + for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent + : callbacks.entrySet()) { + OnOpModeChangedListener cb = ent.getKey(); ArrayList<ChangeRec> reports = ent.getValue(); for (int i=0; i<reports.size(); i++) { ChangeRec rep = reports.get(i); @@ -3121,7 +2999,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) { final UidState uidState = mUidStates.valueAt(uidi); if (uidState.foregroundOps != null) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } } } @@ -3169,20 +3047,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mModeWatchers.put(callback.asBinder(), cb); } if (switchOp != AppOpsManager.OP_NONE) { - ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp); - if (cbs == null) { - cbs = new ArraySet<>(); - mOpModeWatchers.put(switchOp, cbs); - } - cbs.add(cb); + mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp); } if (mayWatchPackageName) { - ArraySet<ModeCallback> cbs = mPackageModeWatchers.get(packageName); - if (cbs == null) { - cbs = new ArraySet<>(); - mPackageModeWatchers.put(packageName, cbs); - } - cbs.add(cb); + mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName); } evalAllForegroundOpsLocked(); } @@ -3197,21 +3065,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch ModeCallback cb = mModeWatchers.remove(callback.asBinder()); if (cb != null) { cb.unlinkToDeath(); - for (int i=mOpModeWatchers.size()-1; i>=0; i--) { - ArraySet<ModeCallback> cbs = mOpModeWatchers.valueAt(i); - cbs.remove(cb); - if (cbs.size() <= 0) { - mOpModeWatchers.removeAt(i); - } - } - for (int i=mPackageModeWatchers.size()-1; i>=0; i--) { - ArraySet<ModeCallback> cbs = mPackageModeWatchers.valueAt(i); - cbs.remove(cb); - if (cbs.size() <= 0) { - mPackageModeWatchers.removeAt(i); - } - } + mAppOpsServiceInterface.removeListener(cb); } + evalAllForegroundOpsLocked(); } } @@ -4542,12 +4398,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch AppOpsService::notifyOpChangedForAllPkgsInUid, this, code, uidState.uid, true, null)); } else if (uidState.pkgOps != null) { - final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); - if (callbacks != null) { - for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { - final ModeCallback callback = callbacks.valueAt(cbi); - if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !callback.isWatchingUid(uidState.uid)) { + final ArraySet<OnOpModeChangedListener> listenerSet = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { + final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); + if ((listener.getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !listener.isWatchingUid(uidState.uid)) { continue; } for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { @@ -4558,7 +4416,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChanged, - this, callback, code, uidState.uid, + this, listenerSet.valueAt(cbi), code, uidState.uid, uidState.pkgOps.keyAt(pkgi))); } } @@ -5045,7 +4903,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } if (changed) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } } } @@ -5131,7 +4989,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch XmlUtils.skipCurrentTag(parser); } } - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent, @@ -6122,62 +5980,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } pw.println(); } - if (mOpModeWatchers.size() > 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i=0; i<mOpModeWatchers.size(); i++) { - if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) { - continue; - } - boolean printedOpHeader = false; - ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i); - for (int j=0; j<callbacks.size(); j++) { - final ModeCallback cb = callbacks.valueAt(j); - if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { - continue; - } - needSep = true; - if (!printedHeader) { - pw.println(" Op mode watchers:"); - printedHeader = true; - } - if (!printedOpHeader) { - pw.print(" Op "); - pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i))); - pw.println(":"); - printedOpHeader = true; - } - pw.print(" #"); pw.print(j); pw.print(": "); - pw.println(cb); - } - } - } - if (mPackageModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i=0; i<mPackageModeWatchers.size(); i++) { - if (dumpPackage != null && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) { - continue; - } - needSep = true; - if (!printedHeader) { - pw.println(" Package mode watchers:"); - printedHeader = true; - } - pw.print(" Pkg "); pw.print(mPackageModeWatchers.keyAt(i)); - pw.println(":"); - ArraySet<ModeCallback> callbacks = mPackageModeWatchers.valueAt(i); - for (int j=0; j<callbacks.size(); j++) { - pw.print(" #"); pw.print(j); pw.print(": "); - pw.println(callbacks.valueAt(j)); - } - } + + if (!dumpHistory) { + needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw); } + if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) { boolean printedHeader = false; - for (int i=0; i<mModeWatchers.size(); i++) { + for (int i = 0; i < mModeWatchers.size(); i++) { final ModeCallback cb = mModeWatchers.valueAt(i); if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { + && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) { continue; } needSep = true; @@ -6729,16 +6542,15 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } private void notifyWatchersOfChange(int code, int uid) { - final ArraySet<ModeCallback> clonedCallbacks; + final ArraySet<OnOpModeChangedListener> modeChangedListenerSet; synchronized (this) { - ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); - if (callbacks == null) { + modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (modeChangedListenerSet == null) { return; } - clonedCallbacks = new ArraySet<>(callbacks); } - notifyOpChanged(clonedCallbacks, code, uid, null); + notifyOpChanged(modeChangedListenerSet, code, uid, null); } @Override diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java index cd5ea120f878..c707086fb2fb 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java @@ -14,12 +14,20 @@ * limitations under the License. */ package com.android.server.appop; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager.Mode; +import android.util.ArraySet; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; + +import java.io.PrintWriter; + /** * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. - * In the future this interface will also include mode callbacks and op restrictions. + * This interface also includes functions for added and removing op mode watchers. + * In the future this interface will also include op restrictions. */ public interface AppOpsServiceInterface { /** @@ -95,4 +103,93 @@ public interface AppOpsServiceInterface { * Stop tracking app-op modes for all uid and packages. */ void clearAllModes(); + + /** + * Registers changedListener to listen to op's mode change. + * @param changedListener the listener that must be trigger on the op's mode change. + * @param op op representing the app-op whose mode change needs to be listened to. + */ + void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + + /** + * Registers changedListener to listen to package's app-op's mode change. + * @param changedListener the listener that must be trigger on the mode change. + * @param packageName of the package whose app-op's mode change needs to be listened to. + */ + void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName); + + /** + * Stop the changedListener from triggering on any mode change. + * @param changedListener the listener that needs to be removed. + */ + void removeListener(@NonNull OnOpModeChangedListener changedListener); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for op's mode changes. + * @param op app-op whose mode change is being listened to. + */ + ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes. + * @param packageName of package whose app-op's mode change is being listened to. + */ + ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param changedListener the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param packageName package name that is associated with the app-op + */ + void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, + @Nullable String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed to all packages associated with the uid by + * triggering the appropriate change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param onlyForeground true if only watchers that + * @param callbackToIgnore callback that should be ignored. + */ + void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore); + + /** + * TODO: Move hasForegroundWatchers and foregroundOps into this. + * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param uid for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + + /** + * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param packageName for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps); + + /** + * Dump op mode and package mode listeners and their details. + * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an + * app-op, only the watchers for that app-op are dumped. + * @param dumpUid uid for which we want to dump op mode watchers. + * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name. + * @param printWriter writer to dump to. + */ + boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); + } diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java index c27c0d3de5d7..2d498ea2117c 100644 --- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java +++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java @@ -16,15 +16,39 @@ package com.android.server.appop; +import static android.app.AppOpsManager.OP_NONE; +import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; +import static android.app.AppOpsManager.opRestrictsRead; + +import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; + +import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManager.Mode; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Handler; +import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.function.pooled.PooledLambda; + +import libcore.util.EmptyArray; + +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Objects; /** @@ -33,8 +57,13 @@ import com.android.internal.annotations.VisibleForTesting; */ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface { - // Should be the same object that the AppOpsService is using for locking. + static final String TAG = "LegacyAppOpsServiceInterfaceImpl"; + + // Must be the same object that the AppOpsService is using for locking. final Object mLock; + final Handler mHandler; + final Context mContext; + final SparseArray<int[]> mSwitchedOps; @GuardedBy("mLock") @VisibleForTesting @@ -43,13 +72,25 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface @GuardedBy("mLock") final ArrayMap<String, SparseIntArray> mPackageModes = new ArrayMap<>(); + final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>(); + final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers = + new ArrayMap<>(); + final PersistenceScheduler mPersistenceScheduler; + // Constant meaning that any UID should be matched when dispatching callbacks + private static final int UID_ANY = -2; + + LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler, - @NonNull Object lock) { + @NonNull Object lock, Handler handler, Context context, + SparseArray<int[]> switchedOps) { this.mPersistenceScheduler = persistenceScheduler; this.mLock = lock; + this.mHandler = handler; + this.mContext = context; + this.mSwitchedOps = switchedOps; } @Override @@ -158,7 +199,6 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface } } - @Override public boolean areUidModesDefault(int uid) { synchronized (mLock) { @@ -195,4 +235,335 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface } } -} + @Override + public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, + int op) { + Objects.requireNonNull(changedListener); + synchronized (mLock) { + ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op); + if (modeWatcherSet == null) { + modeWatcherSet = new ArraySet<>(); + mOpModeWatchers.put(op, modeWatcherSet); + } + modeWatcherSet.add(changedListener); + } + } + + @Override + public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName) { + Objects.requireNonNull(changedListener); + Objects.requireNonNull(packageName); + synchronized (mLock) { + ArraySet<OnOpModeChangedListener> modeWatcherSet = + mPackageModeWatchers.get(packageName); + if (modeWatcherSet == null) { + modeWatcherSet = new ArraySet<>(); + mPackageModeWatchers.put(packageName, modeWatcherSet); + } + modeWatcherSet.add(changedListener); + } + } + + @Override + public void removeListener(@NonNull OnOpModeChangedListener changedListener) { + Objects.requireNonNull(changedListener); + + synchronized (mLock) { + for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) { + ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i); + cbs.remove(changedListener); + if (cbs.size() <= 0) { + mOpModeWatchers.removeAt(i); + } + } + + for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) { + ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i); + cbs.remove(changedListener); + if (cbs.size() <= 0) { + mPackageModeWatchers.removeAt(i); + } + } + } + } + + @Override + public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) { + synchronized (mLock) { + ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op); + if (modeChangedListenersSet == null) { + return new ArraySet<>(); + } + return new ArraySet<>(modeChangedListenersSet); + } + } + + @Override + public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners( + @NonNull String packageName) { + Objects.requireNonNull(packageName); + + synchronized (mLock) { + ArraySet<OnOpModeChangedListener> modeChangedListenersSet = + mPackageModeWatchers.get(packageName); + if (modeChangedListenersSet == null) { + return new ArraySet<>(); + } + return new ArraySet<>(modeChangedListenersSet); + } + } + + @Override + public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code, + int uid, @Nullable String packageName) { + Objects.requireNonNull(onModeChangedListener); + + if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0 + && onModeChangedListener.getWatchingUid() != uid) { + return; + } + + // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE + int[] switchedCodes; + if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) { + switchedCodes = mSwitchedOps.get(code); + } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) { + switchedCodes = new int[]{code}; + } else { + switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()}; + } + + for (int switchedCode : switchedCodes) { + // There are features watching for mode changes such as window manager + // and location manager which are in our process. The callbacks in these + // features may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(), + onModeChangedListener.getCallingUid())) { + continue; + } + onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName); + } catch (RemoteException e) { + /* ignore */ + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) { + // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission, + // as watcher should not use this to signal if the value is changed. + return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS, + watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED; + } + + @Override + public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore) { + String[] uidPackageNames = getPackagesForUid(uid); + ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null; + + synchronized (mLock) { + ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code); + if (callbacks != null) { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + OnOpModeChangedListener callback = callbacks.valueAt(i); + + if (onlyForeground && (callback.getFlags() + & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + + ArraySet<String> changedPackages = new ArraySet<>(); + Collections.addAll(changedPackages, uidPackageNames); + if (callbackSpecs == null) { + callbackSpecs = new ArrayMap<>(); + } + callbackSpecs.put(callback, changedPackages); + } + } + + for (String uidPackageName : uidPackageNames) { + callbacks = mPackageModeWatchers.get(uidPackageName); + if (callbacks != null) { + if (callbackSpecs == null) { + callbackSpecs = new ArrayMap<>(); + } + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + OnOpModeChangedListener callback = callbacks.valueAt(i); + + if (onlyForeground && (callback.getFlags() + & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + + ArraySet<String> changedPackages = callbackSpecs.get(callback); + if (changedPackages == null) { + changedPackages = new ArraySet<>(); + callbackSpecs.put(callback, changedPackages); + } + changedPackages.add(uidPackageName); + } + } + } + + if (callbackSpecs != null && callbackToIgnore != null) { + callbackSpecs.remove(callbackToIgnore); + } + } + + if (callbackSpecs == null) { + return; + } + + for (int i = 0; i < callbackSpecs.size(); i++) { + final OnOpModeChangedListener callback = callbackSpecs.keyAt(i); + final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i); + if (reportedPackageNames == null) { + mHandler.sendMessage(PooledLambda.obtainMessage( + LegacyAppOpsServiceInterfaceImpl::notifyOpChanged, + this, callback, code, uid, (String) null)); + + } else { + final int reportedPackageCount = reportedPackageNames.size(); + for (int j = 0; j < reportedPackageCount; j++) { + final String reportedPackageName = reportedPackageNames.valueAt(j); + mHandler.sendMessage(PooledLambda.obtainMessage( + LegacyAppOpsServiceInterfaceImpl::notifyOpChanged, + this, callback, code, uid, reportedPackageName)); + } + } + } + } + + private static String[] getPackagesForUid(int uid) { + String[] packageNames = null; + + // Very early during boot the package manager is not yet or not yet fully started. At this + // time there are no packages yet. + if (AppGlobals.getPackageManager() != null) { + try { + packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); + } catch (RemoteException e) { + /* ignore - local call */ + } + } + if (packageNames == null) { + return EmptyArray.STRING; + } + return packageNames; + } + + @Override + public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) { + synchronized (mLock) { + return evalForegroundOps(mUidModes.get(uid), foregroundOps); + } + } + + @Override + public SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps) { + synchronized (mLock) { + return evalForegroundOps(mPackageModes.get(packageName), foregroundOps); + } + } + + private SparseBooleanArray evalForegroundOps(SparseIntArray opModes, + SparseBooleanArray foregroundOps) { + SparseBooleanArray tempForegroundOps = foregroundOps; + if (opModes != null) { + for (int i = opModes.size() - 1; i >= 0; i--) { + if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) { + if (tempForegroundOps == null) { + tempForegroundOps = new SparseBooleanArray(); + } + evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps); + } + } + } + return tempForegroundOps; + } + + private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) { + boolean curValue = foregroundOps.get(op, false); + ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) { + if ((listenerSet.valueAt(cbi).getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) { + curValue = true; + } + } + } + foregroundOps.put(op, curValue); + } + + @Override + public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, + PrintWriter printWriter) { + boolean needSep = false; + if (mOpModeWatchers.size() > 0) { + boolean printedHeader = false; + for (int i = 0; i < mOpModeWatchers.size(); i++) { + if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) { + continue; + } + boolean printedOpHeader = false; + ArraySet<OnOpModeChangedListener> modeChangedListenerSet = + mOpModeWatchers.valueAt(i); + for (int j = 0; j < modeChangedListenerSet.size(); j++) { + final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j); + if (dumpPackage != null + && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) { + continue; + } + needSep = true; + if (!printedHeader) { + printWriter.println(" Op mode watchers:"); + printedHeader = true; + } + if (!printedOpHeader) { + printWriter.print(" Op "); + printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i))); + printWriter.println(":"); + printedOpHeader = true; + } + printWriter.print(" #"); printWriter.print(j); printWriter.print(": "); + printWriter.println(listener.toString()); + } + } + } + + if (mPackageModeWatchers.size() > 0 && dumpOp < 0) { + boolean printedHeader = false; + for (int i = 0; i < mPackageModeWatchers.size(); i++) { + if (dumpPackage != null + && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) { + continue; + } + needSep = true; + if (!printedHeader) { + printWriter.println(" Package mode watchers:"); + printedHeader = true; + } + printWriter.print(" Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i)); + printWriter.println(":"); + ArraySet<OnOpModeChangedListener> modeChangedListenerSet = + mPackageModeWatchers.valueAt(i); + + for (int j = 0; j < modeChangedListenerSet.size(); j++) { + printWriter.print(" #"); printWriter.print(j); printWriter.print(": "); + printWriter.println(modeChangedListenerSet.valueAt(j).toString()); + } + } + } + return needSep; + } + +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java new file mode 100644 index 000000000000..5ebe8119f046 --- /dev/null +++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import android.os.RemoteException; + +/** + * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode + * change. + */ +abstract class OnOpModeChangedListener { + + // Constant meaning that any UID should be matched when dispatching callbacks + private static final int UID_ANY = -2; + + private int mWatchingUid; + private int mFlags; + private int mWatchedOpCode; + private int mCallingUid; + private int mCallingPid; + + OnOpModeChangedListener(int watchingUid, int flags, int watchedOpCode, int callingUid, + int callingPid) { + this.mWatchingUid = watchingUid; + this.mFlags = flags; + this.mWatchedOpCode = watchedOpCode; + this.mCallingUid = callingUid; + this.mCallingPid = callingPid; + } + + /** + * Returns the user id that is watching for the mode change. + */ + public int getWatchingUid() { + return mWatchingUid; + } + + /** + * Returns the flags associated with the mode change listener. + */ + public int getFlags() { + return mFlags; + } + + /** + * Get the app-op whose mode change should trigger the callback. + */ + public int getWatchedOpCode() { + return mWatchedOpCode; + } + + /** + * Get the user-id that triggered the app-op mode change to be watched. + */ + public int getCallingUid() { + return mCallingUid; + } + + /** + * Get the process-id that triggered the app-op mode change to be watched. + */ + public int getCallingPid() { + return mCallingPid; + } + + /** + * returns true if the user id passed in the param is the one that is watching for op mode + * changed. + */ + public boolean isWatchingUid(int uid) { + return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid; + } + + /** + * Method that should be triggered when the app-op's mode is changed. + * @param op app-op whose mode-change is being listened to. + * @param uid user-is associated with the app-op. + * @param packageName package name associated with the app-op. + */ + public abstract void onOpModeChanged(int op, int uid, String packageName) + throws RemoteException; + + /** + * Return human readable string representing the listener. + */ + public abstract String toString(); + +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index eba9c7a5f14e..e97d9c22620e 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1771,8 +1771,8 @@ import java.util.concurrent.atomic.AtomicBoolean; return; } Log.w(TAG, "Communication client died"); - removeCommunicationRouteClient(client.getBinder(), true); - onUpdateCommunicationRouteClient("onCommunicationRouteClientDied"); + setCommunicationRouteForClient(client.getBinder(), client.getPid(), null, + BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied"); } /** diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index aedbe4eb945a..b7e817e15452 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -729,8 +729,11 @@ public class SpatializerHelper { } private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) { + // modeForDevice will be neither transaural or binaural for devices that do not support + // spatial audio. For instance mono devices like earpiece, speaker safe or sco must + // not be included. final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(), - /*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL); + /*default when type not found*/ -1); if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL && mTransauralSupported)) { @@ -1538,8 +1541,8 @@ public class SpatializerHelper { @Override public String toString() { - return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled - + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled; + return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled + + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; } String toPersistableString() { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java new file mode 100644 index 000000000000..0f1fe68ad1d7 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java @@ -0,0 +1,61 @@ +/* + * 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.biometrics.sensors; + +import android.annotation.NonNull; +import android.hardware.biometrics.SensorPropertiesInternal; +import android.util.proto.ProtoOutputStream; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Common attributes for all biometric service providers. + * + * @param <T> Internal settings type. + */ +public interface BiometricServiceProvider<T extends SensorPropertiesInternal> { + + /** Checks if the specified sensor is owned by this provider. */ + boolean containsSensor(int sensorId); + + /** All sensor properties. */ + @NonNull + List<T> getSensorProperties(); + + /** Properties for the given sensor id. */ + @NonNull + T getSensorProperties(int sensorId); + + boolean isHardwareDetected(int sensorId); + + /** If the user has any enrollments for the given sensor. */ + boolean hasEnrollments(int sensorId, int userId); + + long getAuthenticatorId(int sensorId, int userId); + + @LockoutTracker.LockoutMode + int getLockoutModeForUser(int sensorId, int userId); + + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer); + + void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd); + + void dumpInternal(int sensorId, @NonNull PrintWriter pw); +} diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java new file mode 100644 index 000000000000..4779f6f931ee --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java @@ -0,0 +1,243 @@ +/* + * 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.biometrics.sensors; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.IBiometricAuthenticator; +import android.hardware.biometrics.IBiometricService; +import android.hardware.biometrics.SensorPropertiesInternal; +import android.os.Handler; +import android.os.IInterface; +import android.os.Process; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Pair; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +/** + * Container for all BiometricServiceProvider implementations. + * + * @param <T> The service provider type. + * @param <P> The internal properties type. + * @param <C> The registration callback for {@link #invokeRegisteredCallback(IInterface, List)}. + */ +public abstract class BiometricServiceRegistry<T extends BiometricServiceProvider<P>, + P extends SensorPropertiesInternal, + C extends IInterface> { + + private static final String TAG = "BiometricServiceRegistry"; + + // Volatile so they can be read without a lock once all services are registered. + // But, ideally remove this and provide immutable copies via the callback instead. + @Nullable + private volatile List<T> mServiceProviders; + @Nullable + private volatile List<P> mAllProps; + + @NonNull + private final Supplier<IBiometricService> mBiometricServiceSupplier; + @NonNull + private final RemoteCallbackList<C> mRegisteredCallbacks = new RemoteCallbackList<>(); + + public BiometricServiceRegistry(@NonNull Supplier<IBiometricService> biometricSupplier) { + mBiometricServiceSupplier = biometricSupplier; + } + + /** + * Register an implementation by creating a new authenticator and initializing it via + * {@link IBiometricService#registerAuthenticator(int, int, int, IBiometricAuthenticator)} + * using the given properties. + * + * @param service service to register with + * @param props internal properties to initialize the authenticator + */ + protected abstract void registerService(@NonNull IBiometricService service, @NonNull P props); + + /** + * Invoke the callback to notify clients that all authenticators have been registered. + * + * @param callback callback to invoke + * @param allProps properties of all authenticators + */ + protected abstract void invokeRegisteredCallback(@NonNull C callback, + @NonNull List<P> allProps) throws RemoteException; + + /** + * Register all authenticators in a background thread. + * + * @param serviceProvider Supplier function that will be invoked on the background thread. + */ + public void registerAll(Supplier<List<T>> serviceProvider) { + // Some HAL might not be started before the system service and will cause the code below + // to wait, and some of the operations below might take a significant amount of time to + // complete (calls to the HALs). To avoid blocking the rest of system server we put + // this on a background thread. + final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, + true /* allowIo */); + thread.start(); + final Handler handler = new Handler(thread.getLooper()); + handler.post(() -> registerAllInBackground(serviceProvider)); + thread.quitSafely(); + } + + /** Register authenticators now, only called by {@link #registerAll(Supplier).} */ + @VisibleForTesting + public void registerAllInBackground(Supplier<List<T>> serviceProvider) { + List<T> providers = serviceProvider.get(); + if (providers == null) { + providers = new ArrayList<>(); + } + + final IBiometricService biometricService = mBiometricServiceSupplier.get(); + if (biometricService == null) { + throw new IllegalStateException("biometric service cannot be null"); + } + + // Register each sensor individually with BiometricService + final List<P> allProps = new ArrayList<>(); + for (T provider : providers) { + final List<P> props = provider.getSensorProperties(); + for (P prop : props) { + registerService(biometricService, prop); + } + allProps.addAll(props); + } + + finishRegistration(providers, allProps); + } + + private synchronized void finishRegistration( + @NonNull List<T> providers, @NonNull List<P> allProps) { + mServiceProviders = Collections.unmodifiableList(providers); + mAllProps = Collections.unmodifiableList(allProps); + broadcastAllAuthenticatorsRegistered(); + } + + /** + * Add a callback that will be invoked once the work from {@link #registerAll(Supplier)} + * has finished registering all providers (executes immediately if already done). + * + * @param callback registration callback + */ + public synchronized void addAllRegisteredCallback(@Nullable C callback) { + if (callback == null) { + Slog.e(TAG, "addAllRegisteredCallback, callback is null"); + return; + } + + final boolean registered = mRegisteredCallbacks.register(callback); + final boolean allRegistered = mServiceProviders != null; + if (registered && allRegistered) { + broadcastAllAuthenticatorsRegistered(); + } else if (!registered) { + Slog.e(TAG, "addAllRegisteredCallback failed to register callback"); + } + } + + private synchronized void broadcastAllAuthenticatorsRegistered() { + final int n = mRegisteredCallbacks.beginBroadcast(); + for (int i = 0; i < n; ++i) { + final C cb = mRegisteredCallbacks.getBroadcastItem(i); + try { + invokeRegisteredCallback(cb, mAllProps); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in broadcastAllAuthenticatorsRegistered", e); + } finally { + mRegisteredCallbacks.unregister(cb); + } + } + mRegisteredCallbacks.finishBroadcast(); + } + + /** + * Get a list of registered providers. + * + * Undefined until {@link #registerAll(Supplier)} has fired the completion callback. + */ + @NonNull + public List<T> getProviders() { + return mServiceProviders != null ? mServiceProviders : Collections.emptyList(); + } + + /** + * Gets the provider for given sensor id or null if not registered. + * + * Undefined until {@link #registerAll(Supplier)} has fired the completion callback. + */ + @Nullable + public T getProviderForSensor(int sensorId) { + if (mServiceProviders != null) { + for (T provider : mServiceProviders) { + if (provider.containsSensor(sensorId)) { + return provider; + } + } + } + return null; + } + + /** + * Finds the provider for devices with only a single sensor. + * + * If no providers returns null. If multiple sensors are present this method + * will return the first one that is found (this is a legacy for test devices that + * use aidl/hidl concurrently and should not occur on real devices). + * + * Undefined until {@link #registerAll(Supplier)} has fired the completion callback. + */ + @Nullable + public Pair<Integer, T> getSingleProvider() { + if (mAllProps == null || mAllProps.isEmpty()) { + Slog.e(TAG, "No sensors found"); + return null; + } + + if (mAllProps.size() > 1) { + Slog.e(TAG, "getSingleProvider() called but multiple sensors present: " + + mAllProps.size()); + } + + final int sensorId = mAllProps.get(0).sensorId; + final T provider = getProviderForSensor(sensorId); + if (provider != null) { + return new Pair<>(sensorId, provider); + } + + Slog.e(TAG, "Single sensor: " + sensorId + ", but provider not found"); + return null; + } + + /** + * Get the properties for all providers. + * + * Undefined until {@link #registerAll(Supplier)} has fired the completion callback. + */ + @NonNull + public List<P> getAllProperties() { + return mAllProps != null ? mAllProps : Collections.emptyList(); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java index 0d789f7a1840..f8543162f95e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java @@ -23,32 +23,64 @@ import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE; import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.IBiometricStateListener; +import android.hardware.biometrics.SensorPropertiesInternal; import android.os.RemoteException; +import android.os.UserManager; import android.util.Slog; import com.android.server.biometrics.Utils; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * A callback for receiving notifications about biometric sensor state changes. + * + * @param <T> service provider type + * @param <P> internal property type */ -public class BiometricStateCallback implements ClientMonitorCallback { +public class BiometricStateCallback<T extends BiometricServiceProvider<P>, + P extends SensorPropertiesInternal> implements ClientMonitorCallback { private static final String TAG = "BiometricStateCallback"; @NonNull - private final CopyOnWriteArrayList<IBiometricStateListener> - mBiometricStateListeners = new CopyOnWriteArrayList<>(); - - private @BiometricStateListener.State int mBiometricState; + private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners = + new CopyOnWriteArrayList<>(); + @NonNull + private final UserManager mUserManager; + @BiometricStateListener.State + private int mBiometricState; + @NonNull + private List<T> mProviders = List.of(); - public BiometricStateCallback() { + /** + * Create a new callback that must be {@link #start(List)}ed. + * + * @param userManager user manager + */ + public BiometricStateCallback(@NonNull UserManager userManager) { mBiometricState = STATE_IDLE; + mUserManager = userManager; + } + + /** + * This should be called when the service has been initialized and all providers are ready. + * + * @param allProviders all registered biometric service providers + */ + public synchronized void start(@NonNull List<T> allProviders) { + mProviders = Collections.unmodifiableList(allProviders); + broadcastCurrentEnrollmentState(null /* listener */); } + /** Get the current state. */ + @BiometricStateListener.State public int getBiometricState() { return mBiometricState; } @@ -120,23 +152,43 @@ public class BiometricStateCallback implements ClientMonitorCallback { } /** - * This should be invoked when: - * 1) Enrolled --> None-enrolled - * 2) None-enrolled --> enrolled - * 3) HAL becomes ready - * 4) Listener is registered + * Enables clients to register a BiometricStateListener. For example, this is used to forward + * fingerprint sensor state changes to SideFpsEventHandler. + * + * @param listener listener to register */ - public void notifyAllEnrollmentStateChanged(int userId, int sensorId, + public synchronized void registerBiometricStateListener( + @NonNull IBiometricStateListener listener) { + mBiometricStateListeners.add(listener); + broadcastCurrentEnrollmentState(listener); + } + + private synchronized void broadcastCurrentEnrollmentState( + @Nullable IBiometricStateListener listener) { + for (T provider : mProviders) { + for (SensorPropertiesInternal prop : provider.getSensorProperties()) { + for (UserInfo userInfo : mUserManager.getAliveUsers()) { + final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id); + if (listener != null) { + notifyEnrollmentStateChanged( + listener, userInfo.id, prop.sensorId, enrolled); + } else { + notifyAllEnrollmentStateChanged( + userInfo.id, prop.sensorId, enrolled); + } + } + } + } + } + + private void notifyAllEnrollmentStateChanged(int userId, int sensorId, boolean hasEnrollments) { for (IBiometricStateListener listener : mBiometricStateListeners) { notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments); } } - /** - * Notifies the listener of enrollment state changes. - */ - public void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener, + private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener, int userId, int sensorId, boolean hasEnrollments) { try { listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments); @@ -144,14 +196,4 @@ public class BiometricStateCallback implements ClientMonitorCallback { Slog.e(TAG, "Remote exception", e); } } - - /** - * Enables clients to register a BiometricStateListener. For example, this is used to forward - * fingerprint sensor state changes to SideFpsEventHandler. - * - * @param listener - */ - public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { - mBiometricStateListeners.add(listener); - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 79e65cc6d2e5..271bce9890c6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -17,20 +17,18 @@ package com.android.server.biometrics.sensors.face; import static android.Manifest.permission.INTERACT_ACROSS_USERS; -import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.MANAGE_FACE; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricStateListener; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; @@ -39,18 +37,18 @@ import android.hardware.biometrics.face.SensorProps; import android.hardware.face.Face; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.FaceServiceReceiver; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; import android.os.NativeHandle; -import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; +import android.os.UserManager; import android.util.Pair; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -58,10 +56,10 @@ import android.view.Surface; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -88,51 +86,10 @@ public class FaceService extends SystemService { private final LockoutResetDispatcher mLockoutResetDispatcher; private final LockPatternUtils mLockPatternUtils; @NonNull - private final List<ServiceProvider> mServiceProviders; - - @Nullable - private ServiceProvider getProviderForSensor(int sensorId) { - for (ServiceProvider provider : mServiceProviders) { - if (provider.containsSensor(sensorId)) { - return provider; - } - } - return null; - } - - /** - * For devices with only a single provider, returns that provider. If no providers, or multiple - * providers exist, returns null. - */ - @Nullable - private Pair<Integer, ServiceProvider> getSingleProvider() { - final List<FaceSensorPropertiesInternal> properties = getSensorProperties(); - if (properties.size() != 1) { - Slog.e(TAG, "Multiple sensors found: " + properties.size()); - return null; - } - - // Theoretically we can just return the first provider, but maybe this is easier to - // understand. - final int sensorId = properties.get(0).sensorId; - for (ServiceProvider provider : mServiceProviders) { - if (provider.containsSensor(sensorId)) { - return new Pair<>(sensorId, provider); - } - } - - Slog.e(TAG, "Single sensor, but provider not found"); - return null; - } - + private final FaceServiceRegistry mRegistry; @NonNull - private List<FaceSensorPropertiesInternal> getSensorProperties() { - final List<FaceSensorPropertiesInternal> properties = new ArrayList<>(); - for (ServiceProvider provider : mServiceProviders) { - properties.addAll(provider.getSensorProperties()); - } - return properties; - } + private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal> + mBiometricStateCallback; /** * Receives the incoming binder calls from FaceManager. @@ -142,8 +99,7 @@ public class FaceService extends SystemService { @Override public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId); @@ -156,9 +112,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) { - final ProtoOutputStream proto = new ProtoOutputStream(); - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider != null) { provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer); } @@ -170,16 +125,14 @@ public class FaceService extends SystemService { @Override // Binder call public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal( String opPackageName) { - - return FaceService.this.getSensorProperties(); + return mRegistry.getAllProperties(); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public FaceSensorPropertiesInternal getSensorProperties(int sensorId, @NonNull String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId + ", caller: " + opPackageName); @@ -193,8 +146,7 @@ public class FaceService extends SystemService { @Override // Binder call public void generateChallenge(IBinder token, int sensorId, int userId, IFaceServiceReceiver receiver, String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId); return; @@ -207,8 +159,7 @@ public class FaceService extends SystemService { @Override // Binder call public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId); return; @@ -222,8 +173,7 @@ public class FaceService extends SystemService { public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); return -1; @@ -245,8 +195,7 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC) @Override // Binder call public void cancelEnrollment(final IBinder token, long requestId) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelEnrollment"); return; @@ -260,7 +209,6 @@ public class FaceService extends SystemService { public long authenticate(final IBinder token, final long operationId, int userId, final IFaceServiceReceiver receiver, final String opPackageName, boolean isKeyguardBypassEnabled) { - // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or // lockdown, something wrong happened. See similar path in FingerprintService. @@ -273,7 +221,7 @@ public class FaceService extends SystemService { // permission. final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName); - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for authenticate"); return -1; @@ -301,7 +249,7 @@ public class FaceService extends SystemService { return -1; } - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFace"); return -1; @@ -318,8 +266,7 @@ public class FaceService extends SystemService { IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for prepareForAuthentication"); return; @@ -336,8 +283,7 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public void startPreparedClient(int sensorId, int cookie) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for startPreparedClient"); return; @@ -350,8 +296,7 @@ public class FaceService extends SystemService { @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName, final long requestId) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelAuthentication"); return; @@ -370,7 +315,7 @@ public class FaceService extends SystemService { return; } - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelFaceDetect"); return; @@ -383,8 +328,7 @@ public class FaceService extends SystemService { @Override // Binder call public void cancelAuthenticationFromService(int sensorId, final IBinder token, final String opPackageName, final long requestId) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for cancelAuthenticationFromService"); return; @@ -397,8 +341,7 @@ public class FaceService extends SystemService { @Override // Binder call public void remove(final IBinder token, final int faceId, final int userId, final IFaceServiceReceiver receiver, final String opPackageName) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for remove"); return; @@ -412,7 +355,6 @@ public class FaceService extends SystemService { @Override // Binder call public void removeAll(final IBinder token, final int userId, final IFaceServiceReceiver receiver, final String opPackageName) { - final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() { int sensorsFinishedRemoving = 0; final int numSensors = getSensorPropertiesInternal( @@ -432,7 +374,7 @@ public class FaceService extends SystemService { // This effectively iterates through all sensors, but has to do so by finding all // sensors under each provider. - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { List<FaceSensorPropertiesInternal> props = provider.getSensorProperties(); for (FaceSensorPropertiesInternal prop : props) { provider.scheduleRemoveAll(prop.sensorId, token, userId, internalReceiver, @@ -467,27 +409,27 @@ public class FaceService extends SystemService { try { if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) { final ProtoOutputStream proto = new ProtoOutputStream(fd); - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) { provider.dumpProtoState(props.sensorId, proto, false); } } proto.flush(); } else if (args.length > 0 && "--proto".equals(args[0])) { - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) { provider.dumpProtoMetrics(props.sensorId, fd); } } } else if (args.length > 1 && "--hal".equals(args[0])) { - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) { provider.dumpHal(props.sensorId, fd, Arrays.copyOfRange(args, 1, args.length, args.getClass())); } } } else { - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) { pw.println("Dumping for sensorId: " + props.sensorId + ", provider: " + provider.getClass().getSimpleName()); @@ -504,10 +446,9 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public boolean isHardwareDetected(int sensorId, String opPackageName) { - final long token = Binder.clearCallingIdentity(); try { - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName); return false; @@ -521,12 +462,11 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) { - if (userId != UserHandle.getCallingUserId()) { Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS); } - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getEnrolledFaces, caller: " + opPackageName); return Collections.emptyList(); @@ -538,12 +478,11 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) { - if (userId != UserHandle.getCallingUserId()) { Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS); } - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for hasEnrolledFaces, caller: " + opPackageName); return false; @@ -555,8 +494,7 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getLockoutModeForUser"); return LockoutTracker.LOCKOUT_NONE; @@ -569,8 +507,7 @@ public class FaceService extends SystemService { @Override public void invalidateAuthenticatorId(int sensorId, int userId, IInvalidationCallback callback) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for invalidateAuthenticatorId"); return; @@ -582,7 +519,7 @@ public class FaceService extends SystemService { @Override // Binder call public long getAuthenticatorId(int sensorId, int userId) { - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getAuthenticatorId"); return 0; @@ -595,8 +532,7 @@ public class FaceService extends SystemService { @Override // Binder call public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken, String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName); return; @@ -610,8 +546,7 @@ public class FaceService extends SystemService { public void setFeature(final IBinder token, int userId, int feature, boolean enabled, final byte[] hardwareAuthToken, IFaceServiceReceiver receiver, final String opPackageName) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for setFeature"); return; @@ -625,8 +560,7 @@ public class FaceService extends SystemService { @Override public void getFeature(final IBinder token, int userId, int feature, IFaceServiceReceiver receiver, final String opPackageName) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for getFeature"); return; @@ -636,18 +570,14 @@ public class FaceService extends SystemService { new ClientMonitorCallbackConverter(receiver), opPackageName); } - private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) { - for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { - mServiceProviders.add( - Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher)); - } - } + private List<ServiceProvider> getAidlProviders() { + final List<ServiceProvider> providers = new ArrayList<>(); - private void addAidlProviders() { final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR); if (instances == null || instances.length == 0) { - return; + return providers; } + for (String instance : instances) { final String fqName = IFace.DESCRIPTOR + "/" + instance; final IFace face = IFace.Stub.asInterface( @@ -660,53 +590,41 @@ public class FaceService extends SystemService { final SensorProps[] props = face.getSensorProps(); final FaceProvider provider = new FaceProvider(getContext(), props, instance, mLockoutResetDispatcher, BiometricContext.getInstance(getContext())); - mServiceProviders.add(provider); + providers.add(provider); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); } } + + return providers; } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public void registerAuthenticators( @NonNull List<FaceSensorPropertiesInternal> hidlSensors) { - - // Some HAL might not be started before the system service and will cause the code below - // to wait, and some of the operations below might take a significant amount of time to - // complete (calls to the HALs). To avoid blocking the rest of system server we put - // this on a background thread. - final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, - true /* allowIo */); - thread.start(); - final Handler handler = new Handler(thread.getLooper()); - - handler.post(() -> { - addHidlProviders(hidlSensors); - addAidlProviders(); - - final IBiometricService biometricService = IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE)); - - // Register each sensor individually with BiometricService - for (ServiceProvider provider : mServiceProviders) { - final List<FaceSensorPropertiesInternal> props = provider.getSensorProperties(); - for (FaceSensorPropertiesInternal prop : props) { - final int sensorId = prop.sensorId; - final @BiometricManager.Authenticators.Types int strength = - Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength); - final FaceAuthenticator authenticator = new FaceAuthenticator( - mServiceWrapper, sensorId); - try { - biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength, - authenticator); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId); - } - } + mRegistry.registerAll(() -> { + final List<ServiceProvider> providers = new ArrayList<>(); + for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { + providers.add( + Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher)); } + providers.addAll(getAidlProviders()); + return providers; }); } + + @Override + public void addAuthenticatorsRegisteredCallback( + IFaceAuthenticatorsRegisteredCallback callback) { + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mRegistry.addAllRegisteredCallback(callback); + } + + @Override + public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { + mBiometricStateCallback.registerBiometricStateListener(listener); + } } public FaceService(Context context) { @@ -714,7 +632,16 @@ public class FaceService extends SystemService { mServiceWrapper = new FaceServiceWrapper(); mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); - mServiceProviders = new ArrayList<>(); + mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context)); + mRegistry = new FaceServiceRegistry(mServiceWrapper, + () -> IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE))); + mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) { + mBiometricStateCallback.start(mRegistry.getProviders()); + } + }); } @Override @@ -752,7 +679,7 @@ public class FaceService extends SystemService { if (Utils.isVirtualEnabled(getContext())) { Slog.i(TAG, "Sync virtual enrollments"); final int userId = ActivityManager.getCurrentUser(); - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) { provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */, true /* favorHalEnrollments */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java new file mode 100644 index 000000000000..0f0a81d24473 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java @@ -0,0 +1,71 @@ +/* + * 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.biometrics.sensors.face; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.IBiometricService; +import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; +import android.hardware.face.IFaceService; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.BiometricServiceRegistry; + +import java.util.List; +import java.util.function.Supplier; + +/** Registry for {@link IFaceService} providers. */ +public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvider, + FaceSensorPropertiesInternal, IFaceAuthenticatorsRegisteredCallback> { + + private static final String TAG = "FaceServiceRegistry"; + + @NonNull + private final IFaceService mService; + + /** Creates a new registry tied to the given service. */ + public FaceServiceRegistry(@NonNull IFaceService service, + @Nullable Supplier<IBiometricService> biometricSupplier) { + super(biometricSupplier); + mService = service; + } + + @Override + protected void registerService(@NonNull IBiometricService service, + @NonNull FaceSensorPropertiesInternal props) { + @BiometricManager.Authenticators.Types final int strength = + Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength); + try { + service.registerAuthenticator(props.sensorId, TYPE_FACE, strength, + new FaceAuthenticator(mService, props.sensorId)); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId); + } + } + + @Override + protected void invokeRegisteredCallback(@NonNull IFaceAuthenticatorsRegisteredCallback callback, + @NonNull List<FaceSensorPropertiesInternal> allProps) throws RemoteException { + callback.onAllAuthenticatorsRegistered(allProps); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index 6f98365332e2..4efaedbd5530 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -26,15 +26,13 @@ import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.IBinder; -import android.util.proto.ProtoOutputStream; import android.view.Surface; +import com.android.server.biometrics.sensors.BiometricServiceProvider; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; -import com.android.server.biometrics.sensors.LockoutTracker; import java.io.FileDescriptor; -import java.io.PrintWriter; import java.util.List; /** @@ -56,24 +54,11 @@ import java.util.List; * to check (e.g. via {@link FaceManager#getSensorPropertiesInternal()}) that the code path isn't * taken. ServiceProviders will provide a no-op for unsupported operations to fail safely. */ -public interface ServiceProvider { - /** - * Checks if the specified sensor is owned by this provider. - */ - boolean containsSensor(int sensorId); - - @NonNull - List<FaceSensorPropertiesInternal> getSensorProperties(); - - @NonNull - FaceSensorPropertiesInternal getSensorProperties(int sensorId); +public interface ServiceProvider extends BiometricServiceProvider<FaceSensorPropertiesInternal> { @NonNull List<Face> getEnrolledFaces(int sensorId, int userId); - @LockoutTracker.LockoutMode - int getLockoutModeForUser(int sensorId, int userId); - /** * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to be * invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient} @@ -84,10 +69,6 @@ public interface ServiceProvider { + " this method"); } - long getAuthenticatorId(int sensorId, int userId); - - boolean isHardwareDetected(int sensorId); - void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFaceServiceReceiver receiver, String opPackageName); @@ -142,13 +123,6 @@ public interface ServiceProvider { void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments); - void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, - boolean clearSchedulerBuffer); - - void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd); - - void dumpInternal(int sensorId, @NonNull PrintWriter pw); - @NonNull ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 19d54c84c706..6bff179e8eb7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -285,6 +285,11 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override + public boolean hasEnrollments(int sensorId, int userId) { + return !getEnrolledFaces(sensorId, userId).isEmpty(); + } + + @Override public void scheduleInvalidateAuthenticatorId(int sensorId, int userId, @NonNull IInvalidationCallback callback) { mHandler.post(() -> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 65289122747c..c0a119ff5f1e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -484,6 +484,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override + public boolean hasEnrollments(int sensorId, int userId) { + return !getEnrolledFaces(sensorId, userId).isEmpty(); + } + + @Override @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) { return mLockoutTracker.getLockoutModeForUser(userId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 2ba449ae089e..7e2742edd47a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -17,14 +17,11 @@ package com.android.server.biometrics.sensors.fingerprint; import static android.Manifest.permission.INTERACT_ACROSS_USERS; -import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.MANAGE_FINGERPRINT; -import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR; @@ -36,8 +33,6 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -65,7 +60,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Process; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -79,11 +73,9 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; @@ -115,74 +107,32 @@ public class FingerprintService extends SystemService { protected static final String TAG = "FingerprintService"; - private final Object mLock = new Object(); private final AppOpsManager mAppOps; private final LockoutResetDispatcher mLockoutResetDispatcher; private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; private final LockPatternUtils mLockPatternUtils; - @NonNull private final List<ServiceProvider> mServiceProviders; - @NonNull private final BiometricStateCallback mBiometricStateCallback; - @NonNull private final Handler mHandler; - @NonNull private final BiometricContext mBiometricContext; - @NonNull private final Supplier<IBiometricService> mBiometricServiceSupplier; - @NonNull private final Function<String, IFingerprint> mIFingerprintProvider; - - @GuardedBy("mLock") - @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback> - mAuthenticatorsRegisteredCallbacks; - - @GuardedBy("mLock") - @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps; - - /** - * Registers BiometricStateListener in list stored by FingerprintService - * @param listener new BiometricStateListener being added - */ - public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { - mBiometricStateCallback.registerBiometricStateListener(listener); - broadcastCurrentEnrollmentState(listener); - } - - /** - * @param listener if non-null, notifies only this listener. if null, notifies all listeners - * in {@link BiometricStateCallback}. This is slightly ugly, but reduces - * redundant code. - */ - private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) { - final UserManager um = UserManager.get(getContext()); - synchronized (mLock) { - // Update the new listener with current state of all sensors - for (FingerprintSensorPropertiesInternal prop : mSensorProps) { - final ServiceProvider provider = getProviderForSensor(prop.sensorId); - for (UserInfo userInfo : um.getAliveUsers()) { - final boolean enrolled = !provider - .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty(); - - // Defer this work and allow the loop to release the lock sooner - mHandler.post(() -> { - if (listener != null) { - mBiometricStateCallback.notifyEnrollmentStateChanged( - listener, userInfo.id, prop.sensorId, enrolled); - } else { - mBiometricStateCallback.notifyAllEnrollmentStateChanged( - userInfo.id, prop.sensorId, enrolled); - } - }); - } - } - } - } + @NonNull + private final BiometricContext mBiometricContext; + @NonNull + private final Supplier<String[]> mAidlInstanceNameSupplier; + @NonNull + private final Function<String, IFingerprint> mIFingerprintProvider; + @NonNull + private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal> + mBiometricStateCallback; + @NonNull + private final Handler mHandler; + @NonNull + private final FingerprintServiceRegistry mRegistry; - /** - * Receives the incoming binder calls from FingerprintManager. - */ - private final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() { + /** Receives the incoming binder calls from FingerprintManager. */ + @VisibleForTesting + final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() { @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @Override public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId); @@ -195,9 +145,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) { - final ProtoOutputStream proto = new ProtoOutputStream(); - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider != null) { provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer); } @@ -212,16 +161,14 @@ public class FingerprintService extends SystemService { != PackageManager.PERMISSION_GRANTED) { Utils.checkPermission(getContext(), TEST_BIOMETRIC); } - - return FingerprintService.this.getSensorProperties(); + return mRegistry.getAllProperties(); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId, @NonNull String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId + ", caller: " + opPackageName); @@ -234,8 +181,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public void generateChallenge(IBinder token, int sensorId, int userId, IFingerprintServiceReceiver receiver, String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId); return; @@ -248,8 +194,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId); return; @@ -264,8 +209,7 @@ public class FingerprintService extends SystemService { public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); return -1; @@ -278,8 +222,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @Override // Binder call public void cancelEnrollment(final IBinder token, long requestId) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelEnrollment"); return; @@ -339,10 +282,10 @@ public class FingerprintService extends SystemService { final Pair<Integer, ServiceProvider> provider; if (sensorId == FingerprintManager.SENSOR_ID_ANY) { - provider = getSingleProvider(); + provider = mRegistry.getSingleProvider(); } else { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - provider = new Pair<>(sensorId, getProviderForSensor(sensorId)); + provider = new Pair<>(sensorId, mRegistry.getProviderForSensor(sensorId)); } if (provider == null) { Slog.w(TAG, "Null provider for authenticate"); @@ -374,7 +317,6 @@ public class FingerprintService extends SystemService { final IFingerprintServiceReceiver receiver, final String opPackageName, boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException { - final Context context = getUiContext(); final Context promptContext = context.createPackageContextAsUser( opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId)); @@ -468,7 +410,7 @@ public class FingerprintService extends SystemService { return -1; } - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFingerprint"); return -1; @@ -484,8 +426,7 @@ public class FingerprintService extends SystemService { public void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for prepareForAuthentication"); return; @@ -501,8 +442,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC) @Override // Binder call public void startPreparedClient(int sensorId, int cookie) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for startPreparedClient"); return; @@ -532,7 +472,7 @@ public class FingerprintService extends SystemService { return; } - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelAuthentication"); return; @@ -553,7 +493,7 @@ public class FingerprintService extends SystemService { // For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as // cancelling authentication. - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelFingerprintDetect"); return; @@ -566,11 +506,9 @@ public class FingerprintService extends SystemService { @Override // Binder call public void cancelAuthenticationFromService(final int sensorId, final IBinder token, final String opPackageName, final long requestId) { - - Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId); - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for cancelAuthenticationFromService"); return; @@ -583,8 +521,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public void remove(final IBinder token, final int fingerId, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName) { - - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for remove"); return; @@ -617,7 +554,7 @@ public class FingerprintService extends SystemService { // This effectively iterates through all sensors, but has to do so by finding all // sensors under each provider. - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties(); for (FingerprintSensorPropertiesInternal prop : props) { provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId, @@ -652,7 +589,7 @@ public class FingerprintService extends SystemService { try { if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) { final ProtoOutputStream proto = new ProtoOutputStream(fd); - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) { provider.dumpProtoState(props.sensorId, proto, false); @@ -660,14 +597,14 @@ public class FingerprintService extends SystemService { } proto.flush(); } else if (args.length > 0 && "--proto".equals(args[0])) { - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) { provider.dumpProtoMetrics(props.sensorId, fd); } } } else { - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) { pw.println("Dumping for sensorId: " + props.sensorId @@ -698,7 +635,7 @@ public class FingerprintService extends SystemService { final long token = Binder.clearCallingIdentity(); try { - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: " + opPackageName); @@ -713,8 +650,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public boolean isHardwareDetected(int sensorId, String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName); return false; @@ -730,7 +666,7 @@ public class FingerprintService extends SystemService { return; } - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for rename"); return; @@ -781,8 +717,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName); return false; @@ -794,8 +729,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getLockoutModeForUser"); return LockoutTracker.LOCKOUT_NONE; @@ -807,8 +741,7 @@ public class FingerprintService extends SystemService { @Override public void invalidateAuthenticatorId(int sensorId, int userId, IInvalidationCallback callback) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for invalidateAuthenticatorId"); return; @@ -819,8 +752,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public long getAuthenticatorId(int sensorId, int userId) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getAuthenticatorId"); return 0; @@ -832,8 +764,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public void resetLockout(IBinder token, int sensorId, int userId, @Nullable byte[] hardwareAuthToken, String opPackageName) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName); return; @@ -864,55 +795,38 @@ public class FingerprintService extends SystemService { @Override // Binder call public void registerAuthenticators( @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) { - - // Some HAL might not be started before the system service and will cause the code below - // to wait, and some of the operations below might take a significant amount of time to - // complete (calls to the HALs). To avoid blocking the rest of system server we put - // this on a background thread. - final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, - true /* allowIo */); - thread.start(); - final Handler handler = new Handler(thread.getLooper()); - handler.post(() -> { + mRegistry.registerAll(() -> { + final List<ServiceProvider> providers = new ArrayList<>(); + providers.addAll(getHidlProviders(hidlSensors)); List<String> aidlSensors = new ArrayList<>(); - final String[] instances = - ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR); + final String[] instances = mAidlInstanceNameSupplier.get(); if (instances != null) { aidlSensors.addAll(Lists.newArrayList(instances)); } - registerAuthenticatorsForService(aidlSensors, hidlSensors); + providers.addAll(getAidlProviders( + Utils.filterAvailableHalInstances(getContext(), aidlSensors))); + return providers; }); - thread.quitSafely(); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void addAuthenticatorsRegisteredCallback( IFingerprintAuthenticatorsRegisteredCallback callback) { - if (callback == null) { - Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null"); - return; - } + mRegistry.addAllRegisteredCallback(callback); + } - final boolean registered; - final boolean hasSensorProps; - synchronized (mLock) { - registered = mAuthenticatorsRegisteredCallbacks.register(callback); - hasSensorProps = !mSensorProps.isEmpty(); - } - if (registered && hasSensorProps) { - broadcastAllAuthenticatorsRegistered(); - } else if (!registered) { - Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback"); - } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { + mBiometricStateCallback.registerBiometricStateListener(listener); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId); return; @@ -923,8 +837,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onPointerUp(long requestId, int sensorId) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId); return; @@ -935,8 +848,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onUiReady(long requestId, int sensorId) { - - final ServiceProvider provider = getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId); return; @@ -947,8 +859,7 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { - - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { provider.setUdfpsOverlayController(controller); } } @@ -956,22 +867,15 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setSidefpsController(@NonNull ISidefpsController controller) { - - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { provider.setSidefpsController(controller); } } - @Override - public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { - Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - FingerprintService.this.registerBiometricStateListener(listener); - } - + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onPowerPressed() { - Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { provider.onPowerPressed(); } } @@ -981,6 +885,7 @@ public class FingerprintService extends SystemService { this(context, BiometricContext.getInstance(context), () -> IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), + () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR), (fqName) -> IFingerprint.Stub.asInterface( Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)))); } @@ -988,61 +893,34 @@ public class FingerprintService extends SystemService { @VisibleForTesting FingerprintService(Context context, BiometricContext biometricContext, - Supplier<IBiometricService> biometricServiceProvider, + Supplier<IBiometricService> biometricServiceSupplier, + Supplier<String[]> aidlInstanceNameSupplier, Function<String, IFingerprint> fingerprintProvider) { super(context); mBiometricContext = biometricContext; - mBiometricServiceSupplier = biometricServiceProvider; + mAidlInstanceNameSupplier = aidlInstanceNameSupplier; mIFingerprintProvider = fingerprintProvider; mAppOps = context.getSystemService(AppOpsManager.class); mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher(); mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); - mServiceProviders = new ArrayList<>(); - mBiometricStateCallback = new BiometricStateCallback(); - mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>(); - mSensorProps = new ArrayList<>(); + mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context)); mHandler = new Handler(Looper.getMainLooper()); + mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier); + mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> sensors) { + mBiometricStateCallback.start(mRegistry.getProviders()); + } + }); } - @VisibleForTesting - void registerAuthenticatorsForService(@NonNull List<String> aidlInstanceNames, + @NonNull + private List<ServiceProvider> getHidlProviders( @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) { - addHidlProviders(hidlSensors); - addAidlProviders(Utils.filterAvailableHalInstances(getContext(), aidlInstanceNames)); - - final IBiometricService biometricService = mBiometricServiceSupplier.get(); - - // Register each sensor individually with BiometricService - for (ServiceProvider provider : mServiceProviders) { - final List<FingerprintSensorPropertiesInternal> props = - provider.getSensorProperties(); - for (FingerprintSensorPropertiesInternal prop : props) { - final int sensorId = prop.sensorId; - @BiometricManager.Authenticators.Types final int strength = - Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength); - final FingerprintAuthenticator authenticator = new FingerprintAuthenticator( - mServiceWrapper, sensorId); - try { - biometricService.registerAuthenticator(sensorId, TYPE_FINGERPRINT, - strength, authenticator); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId); - } - } - } - - synchronized (mLock) { - for (ServiceProvider provider : mServiceProviders) { - mSensorProps.addAll(provider.getSensorProperties()); - } - } + final List<ServiceProvider> providers = new ArrayList<>(); - broadcastCurrentEnrollmentState(null); // broadcasts to all listeners - broadcastAllAuthenticatorsRegistered(); - } - - private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) { for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) { final Fingerprint21 fingerprint21; if ((Build.IS_USERDEBUG || Build.IS_ENG) @@ -1059,11 +937,16 @@ public class FingerprintService extends SystemService { mBiometricStateCallback, hidlSensor, mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } - mServiceProviders.add(fingerprint21); + providers.add(fingerprint21); } + + return providers; } - private void addAidlProviders(List<String> instances) { + @NonNull + private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) { + final List<ServiceProvider> providers = new ArrayList<>(); + for (String instance : instances) { final String fqName = IFingerprint.DESCRIPTOR + "/" + instance; final IFingerprint fp = mIFingerprintProvider.apply(fqName); @@ -1075,7 +958,7 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext); Slog.i(TAG, "Adding AIDL provider: " + fqName); - mServiceProviders.add(provider); + providers.add(provider); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); } @@ -1083,38 +966,8 @@ public class FingerprintService extends SystemService { Slog.e(TAG, "Unable to get declared service: " + fqName); } } - } - // Notifies the callbacks that all of the authenticators have been registered and removes the - // invoked callbacks from the callback list. - private void broadcastAllAuthenticatorsRegistered() { - // Make a local copy of the data so it can be used outside of the synchronized block when - // making Binder calls. - final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>(); - final List<FingerprintSensorPropertiesInternal> props; - synchronized (mLock) { - if (!mSensorProps.isEmpty()) { - props = new ArrayList<>(mSensorProps); - } else { - Slog.e(TAG, "mSensorProps is empty"); - return; - } - final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast(); - for (int i = 0; i < n; ++i) { - final IFingerprintAuthenticatorsRegisteredCallback cb = - mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i); - callbacks.add(cb); - mAuthenticatorsRegisteredCallbacks.unregister(cb); - } - mAuthenticatorsRegisteredCallbacks.finishBroadcast(); - } - for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) { - try { - cb.onAllAuthenticatorsRegistered(props); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e); - } - } + return providers; } @Override @@ -1122,51 +975,9 @@ public class FingerprintService extends SystemService { publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper); } - @Nullable - private ServiceProvider getProviderForSensor(int sensorId) { - for (ServiceProvider provider : mServiceProviders) { - if (provider.containsSensor(sensorId)) { - return provider; - } - } - return null; - } - - /** - * For devices with only a single provider, returns that provider. If multiple providers, - * returns the first one. If no providers, returns null. - */ - @Nullable - private Pair<Integer, ServiceProvider> getSingleProvider() { - final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties(); - if (properties.isEmpty()) { - Slog.e(TAG, "No providers found"); - return null; - } - - // Theoretically we can just return the first provider, but maybe this is easier to - // understand. - final int sensorId = properties.get(0).sensorId; - for (ServiceProvider provider : mServiceProviders) { - if (provider.containsSensor(sensorId)) { - return new Pair<>(sensorId, provider); - } - } - - Slog.e(TAG, "Provider not found"); - return null; - } - - @NonNull - private List<FingerprintSensorPropertiesInternal> getSensorProperties() { - synchronized (mLock) { - return mSensorProps; - } - } - @NonNull private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) { - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: " + opPackageName); @@ -1229,7 +1040,7 @@ public class FingerprintService extends SystemService { if (Utils.isVirtualEnabled(getContext())) { Slog.i(TAG, "Sync virtual enrollments"); final int userId = ActivityManager.getCurrentUser(); - for (ServiceProvider provider : mServiceProviders) { + for (ServiceProvider provider : mRegistry.getProviders()) { for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) { provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */, true /* favorHalEnrollments */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java new file mode 100644 index 000000000000..33810b764f23 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java @@ -0,0 +1,72 @@ +/* + * 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.biometrics.sensors.fingerprint; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.IBiometricService; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; +import android.hardware.fingerprint.IFingerprintService; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.BiometricServiceRegistry; + +import java.util.List; +import java.util.function.Supplier; + +/** Registry for {@link IFingerprintService} providers. */ +public class FingerprintServiceRegistry extends BiometricServiceRegistry<ServiceProvider, + FingerprintSensorPropertiesInternal, IFingerprintAuthenticatorsRegisteredCallback> { + + private static final String TAG = "FingerprintServiceRegistry"; + + @NonNull + private final IFingerprintService mService; + + /** Creates a new registry tied to the given service. */ + public FingerprintServiceRegistry(@NonNull IFingerprintService service, + @Nullable Supplier<IBiometricService> biometricSupplier) { + super(biometricSupplier); + mService = service; + } + + @Override + protected void registerService(@NonNull IBiometricService service, + @NonNull FingerprintSensorPropertiesInternal props) { + @BiometricManager.Authenticators.Types final int strength = + Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength); + try { + service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength, + new FingerprintAuthenticator(mService, props.sensorId)); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId); + } + } + + @Override + protected void invokeRegisteredCallback( + @NonNull IFingerprintAuthenticatorsRegisteredCallback callback, + @NonNull List<FingerprintSensorPropertiesInternal> allProps) throws RemoteException { + callback.onAllAuthenticatorsRegistered(allProps); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 275d7e445a75..9075e7ec2080 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -28,14 +28,11 @@ import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; -import android.util.proto.ProtoOutputStream; +import com.android.server.biometrics.sensors.BiometricServiceProvider; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; -import com.android.server.biometrics.sensors.LockoutTracker; -import java.io.FileDescriptor; -import java.io.PrintWriter; import java.util.List; /** @@ -59,23 +56,8 @@ import java.util.List; * fail safely. */ @SuppressWarnings("deprecation") -public interface ServiceProvider { - /** - * Checks if the specified sensor is owned by this provider. - */ - boolean containsSensor(int sensorId); - - @NonNull - List<FingerprintSensorPropertiesInternal> getSensorProperties(); - - /** - * Returns the internal properties of the specified sensor, if owned by this provider. - * - * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor. - * @return An object representing the internal properties of the specified sensor. - */ - @Nullable - FingerprintSensorPropertiesInternal getSensorProperties(int sensorId); +public interface ServiceProvider extends + BiometricServiceProvider<FingerprintSensorPropertiesInternal> { void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken); @@ -126,16 +108,11 @@ public interface ServiceProvider { void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments); - boolean isHardwareDetected(int sensorId); - void rename(int sensorId, int fingerId, int userId, @NonNull String name); @NonNull List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId); - @LockoutTracker.LockoutMode - int getLockoutModeForUser(int sensorId, int userId); - /** * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to * be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient} @@ -143,7 +120,6 @@ public interface ServiceProvider { void scheduleInvalidateAuthenticatorId(int sensorId, int userId, @NonNull IInvalidationCallback callback); - long getAuthenticatorId(int sensorId, int userId); void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major); @@ -161,13 +137,6 @@ public interface ServiceProvider { */ void setSidefpsController(@NonNull ISidefpsController controller); - void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, - boolean clearSchedulerBuffer); - - void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd); - - void dumpInternal(int sensorId, @NonNull PrintWriter pw); - @NonNull ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 2dc005206b42..3fe6332fcaa0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -565,6 +565,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override + public boolean hasEnrollments(int sensorId, int userId) { + return !getEnrolledFingerprints(sensorId, userId).isEmpty(); + } + + @Override public void scheduleInvalidateAuthenticatorId(int sensorId, int userId, @NonNull IInvalidationCallback callback) { mHandler.post(() -> { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index ed482f013869..0e6df8e0df77 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -789,6 +789,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override + public boolean hasEnrollments(int sensorId, int userId) { + return !getEnrolledFingerprints(sensorId, userId).isEmpty(); + } + + @Override @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) { return mLockoutTracker.getLockoutModeForUser(userId); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 2cbdad751b59..247635017539 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -813,13 +813,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; loadFromDisplayDeviceConfig(token, info); - if (DEBUG) { - Trace.beginAsyncSection("DisplayPowerController#updatePowerState", 0); - } updatePowerState(); - if (DEBUG) { - Trace.endAsyncSection("DisplayPowerController#updatePowerState", 0); - } } }); } @@ -1147,6 +1141,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void updatePowerState() { + if (DEBUG) { + Trace.beginSection("DisplayPowerController#updatePowerState"); + } + updatePowerStateInternal(); + if (DEBUG) { + Trace.endSection(); + } + } + + private void updatePowerStateInternal() { // Update the power state request. final boolean mustNotify; final int previousPolicy; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index ea54b3001163..e6c2e7cae23e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -544,6 +544,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { removeAction(ActiveSourceAction.class); + removeAction(OneTouchPlayAction.class); return; } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index fa8b5c1b8086..3ee35036e9fd 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1051,13 +1051,13 @@ public class HdmiControlService extends SystemService { // Address allocation completed for all devices. Notify each device. if (allocatingDevices.size() == ++finished[0]) { - mAddressAllocated = true; if (initiatedBy != INITIATED_BY_HOTPLUG) { // In case of the hotplug we don't call // onInitializeCecComplete() // since we reallocate the logical address only. onInitializeCecComplete(initiatedBy); } + mAddressAllocated = true; notifyAddressAllocated(allocatedDevices, initiatedBy); // Reinvoke the saved display status callback once the local // device is ready. diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 729c521a4ce0..477b8da61e0f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -86,7 +86,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1328,17 +1327,16 @@ public class PreferencesHelper implements RankingConfig { return null; } NotificationChannelGroup group = r.groups.get(groupId).clone(); - ArrayList channels = new ArrayList(); + group.setChannels(new ArrayList<>()); int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (groupId.equals(nc.getGroup())) { - channels.add(nc); + group.addChannel(nc); } } } - group.setChannels(channels); return group; } } @@ -1351,10 +1349,7 @@ public class PreferencesHelper implements RankingConfig { if (r == null) { return null; } - if (r.groups.get(groupId) != null) { - return r.groups.get(groupId).clone(); - } - return null; + return r.groups.get(groupId); } } @@ -1362,48 +1357,44 @@ public class PreferencesHelper implements RankingConfig { public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) { Objects.requireNonNull(pkg); - List<NotificationChannelGroup> groups = new ArrayList<>(); + Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); synchronized (mPackagePreferences) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); } - Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap(); + NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (nc.getGroup() != null) { if (r.groups.get(nc.getGroup()) != null) { - ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault( - nc.getGroup(), new ArrayList<>()); - channels.add(nc); - groupedChannels.put(nc.getGroup(), channels); + NotificationChannelGroup ncg = groups.get(nc.getGroup()); + if (ncg == null) { + ncg = r.groups.get(nc.getGroup()).clone(); + ncg.setChannels(new ArrayList<>()); + groups.put(nc.getGroup(), ncg); + + } + ncg.addChannel(nc); } } else { - ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault( - null, new ArrayList<>()); - channels.add(nc); - groupedChannels.put(null, channels); + nonGrouped.addChannel(nc); } } } - for (NotificationChannelGroup group : r.groups.values()) { - ArrayList<NotificationChannel> channels = - groupedChannels.getOrDefault(group.getId(), new ArrayList<>()); - if (includeEmpty || !channels.isEmpty()) { - NotificationChannelGroup clone = group.clone(); - clone.setChannels(channels); - groups.add(clone); - } + if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { + groups.put(null, nonGrouped); } - - if (includeNonGrouped && groupedChannels.containsKey(null)) { - NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); - nonGrouped.setChannels(groupedChannels.get(null)); - groups.add(nonGrouped); + if (includeEmpty) { + for (NotificationChannelGroup group : r.groups.values()) { + if (!groups.containsKey(group.getId())) { + groups.put(group.getId(), group); + } + } } - return new ParceledListSlice<>(groups); + return new ParceledListSlice<>(new ArrayList<>(groups.values())); } } diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index bdc571103ffd..5e0a18039152 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -131,6 +131,17 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } } + // For tests: just do the setting of various local variables without actually doing work + @VisibleForTesting + protected void initForTests(Context context, NotificationUsageStats usageStats, + LruCache peopleCache) { + mUserToContextMap = new ArrayMap<>(); + mBaseContext = context; + mUsageStats = usageStats; + mPeopleCache = peopleCache; + mEnabled = true; + } + public RankingReconsideration process(NotificationRecord record) { if (!mEnabled) { if (VERBOSE) Slog.i(TAG, "disabled"); @@ -179,7 +190,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return NONE; } final PeopleRankingReconsideration prr = - validatePeople(context, key, extras, null, affinityOut); + validatePeople(context, key, extras, null, affinityOut, null); float affinity = affinityOut[0]; if (prr != null) { @@ -224,15 +235,21 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return context; } - private RankingReconsideration validatePeople(Context context, + @VisibleForTesting + protected RankingReconsideration validatePeople(Context context, final NotificationRecord record) { final String key = record.getKey(); final Bundle extras = record.getNotification().extras; final float[] affinityOut = new float[1]; + ArraySet<String> phoneNumbersOut = new ArraySet<>(); final PeopleRankingReconsideration rr = - validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut); + validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut, + phoneNumbersOut); final float affinity = affinityOut[0]; record.setContactAffinity(affinity); + if (phoneNumbersOut.size() > 0) { + record.mergePhoneNumbers(phoneNumbersOut); + } if (rr == null) { mUsageStats.registerPeopleAffinity(record, affinity > NONE, affinity == STARRED_CONTACT, true /* cached */); @@ -243,7 +260,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras, - List<String> peopleOverride, float[] affinityOut) { + List<String> peopleOverride, float[] affinityOut, ArraySet<String> phoneNumbersOut) { float affinity = NONE; if (extras == null) { return null; @@ -270,6 +287,15 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } if (lookupResult != null) { affinity = Math.max(affinity, lookupResult.getAffinity()); + + // add all phone numbers associated with this lookup result, if they exist + // and if requested + if (phoneNumbersOut != null) { + ArraySet<String> phoneNumbers = lookupResult.getPhoneNumbers(); + if (phoneNumbers != null && phoneNumbers.size() > 0) { + phoneNumbersOut.addAll(phoneNumbers); + } + } } } if (++personIdx == MAX_PEOPLE) { @@ -289,7 +315,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return new PeopleRankingReconsideration(context, key, pendingLookups); } - private String getCacheKey(int userId, String handle) { + @VisibleForTesting + protected static String getCacheKey(int userId, String handle) { return Integer.toString(userId) + ":" + handle; } @@ -485,7 +512,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } } - private static class LookupResult { + @VisibleForTesting + protected static class LookupResult { private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr private final long mExpireMillis; @@ -574,7 +602,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return mPhoneNumbers; } - private boolean isExpired() { + @VisibleForTesting + protected boolean isExpired() { return mExpireMillis < System.currentTimeMillis(); } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 51b36dd9c579..715967369ffb 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -39,7 +39,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; -import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -1417,18 +1416,16 @@ public final class OverlayManagerService extends SystemService { private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages, final int userId) { - final PackageManagerInternal pmInternal = - LocalServices.getService(PackageManagerInternal.class); - final ActivityManagerInternal amInternal = - LocalServices.getService(ActivityManagerInternal.class); CollectionUtils.forEach(targetPackages, target -> { final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, Uri.fromParts("package", target, null)); intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - final int[] allowList = pmInternal.getVisibilityAllowList(target, userId); - amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */, - false /* serialized */, userId, allowList, null /* filterExtrasForReceiver */, - null /* bOptions */); + try { + ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, + null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); + } catch (RemoteException e) { + Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e); + } }); } diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index a878bfd46a54..ab998608f66d 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -113,6 +113,8 @@ public interface Computer extends PackageDataSnapshot { @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits); @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, + long flags, int filterCallingUid, int userId); + @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, long flags, int userId); @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType, long flags, int userId, int callingUid, boolean includeInstantApps); @@ -500,6 +502,13 @@ public interface Computer extends PackageDataSnapshot { boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo, @UserIdInt int userId); + /** + * @return true if the runtime app user enabled state and the install-time app manifest enabled + * state are both effectively enabled for the given app. Or if the app cannot be found, + * returns false. + */ + boolean isApplicationEffectivelyEnabled(@NonNull String packageName, @UserIdInt int userId); + @Nullable KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 8ec3d2bc74ca..7c1777852382 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -608,6 +608,15 @@ public class ComputerEngine implements Computer { resolveForStart, userId, intent); } + @NonNull + @Override + public final List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) { + return queryIntentActivitiesInternal( + intent, resolvedType, flags, 0 /*privateResolveFlags*/, filterCallingUid, + userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); + } + public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { return queryIntentActivitiesInternal( @@ -5412,6 +5421,26 @@ public class ComputerEngine implements Computer { } } + @Override + public boolean isApplicationEffectivelyEnabled(@NonNull String packageName, + @UserIdInt int userId) { + try { + int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId); + if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) { + final AndroidPackage pkg = getPackage(packageName); + if (pkg == null) { + // Should not happen because getApplicationEnabledSetting would have thrown + return false; + } + return pkg.isEnabled(); + } else { + return appEnabledSetting == COMPONENT_ENABLED_STATE_ENABLED; + } + } catch (PackageManager.NameNotFoundException ignored) { + return false; + } + } + @Nullable @Override public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) { diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index c8db2973f1eb..e969d93465f8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -309,7 +309,8 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { public final List<ResolveInfo> queryIntentActivities( Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) { - return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags, userId); + return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags, + filterCallingUid, userId); } @Override diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 5731af68df95..297439f33a8b 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -315,4 +315,10 @@ public abstract class UserManagerInternal { /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */ public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId); + + /** + * Returns {@code true} if the user is visible (as defined by + * {@link UserManager#isUserVisible()} in the given display. + */ + public abstract boolean isUserVisible(@UserIdInt int userId, int displayId); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 025e97318ba9..e423ea9170d7 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.UserManager.DISALLOW_USER_SWITCH; import android.Manifest; import android.accounts.Account; @@ -1885,6 +1886,19 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) { + boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_SWITCHER_ENABLED, + Resources.getSystem().getBoolean(com.android.internal + .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0; + + return UserManager.supportsMultipleUsers() + && !hasUserRestriction(DISALLOW_USER_SWITCH, mUserId) + && !UserManager.isDeviceInDemoMode(mContext) + && multiUserSettingOn; + } + + @Override public boolean isRestricted(@UserIdInt int userId) { if (userId != UserHandle.getCallingUserId()) { checkCreateUsersPermission("query isRestricted for user " + userId); @@ -2277,7 +2291,6 @@ public class UserManagerService extends IUserManager.Stub { originatingUserId, local); localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local, updatedLocalTargetUserIds); - if (isDeviceOwner) { // Remember the global restriction owner userId to be able to make a distinction // in getUserRestrictionSource on who set local policies. @@ -4804,41 +4817,59 @@ public class UserManagerService extends IUserManager.Stub { null, // use default PullAtomMetadata values BackgroundThread.getExecutor(), this::onPullAtom); + statsManager.setPullAtomCallback( + FrameworkStatsLog.MULTI_USER_INFO, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + this::onPullAtom); } /** Writes a UserInfo pulled atom for each user on the device. */ private int onPullAtom(int atomTag, List<StatsEvent> data) { - if (atomTag != FrameworkStatsLog.USER_INFO) { - Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag); - return android.app.StatsManager.PULL_SKIP; - } - final List<UserInfo> users = getUsersInternal(true, true, true); - final int size = users.size(); - if (size > 1) { - for (int idx = 0; idx < size; idx++) { - final UserInfo user = users.get(idx); - final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType); - final String userTypeCustom = (userTypeStandard == FrameworkStatsLog - .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) - ? - user.userType : null; - - boolean isUserRunningUnlocked; - synchronized (mUserStates) { - isUserRunningUnlocked = - mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED; + if (atomTag == FrameworkStatsLog.USER_INFO) { + final List<UserInfo> users = getUsersInternal(true, true, true); + final int size = users.size(); + if (size > 1) { + for (int idx = 0; idx < size; idx++) { + final UserInfo user = users.get(idx); + final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType); + final String userTypeCustom = (userTypeStandard == FrameworkStatsLog + .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) + ? + user.userType : null; + + boolean isUserRunningUnlocked; + synchronized (mUserStates) { + isUserRunningUnlocked = + mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED; + } + + data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO, + user.id, + userTypeStandard, + userTypeCustom, + user.flags, + user.creationTime, + user.lastLoggedInTime, + isUserRunningUnlocked + )); + } + } + } else if (atomTag == FrameworkStatsLog.MULTI_USER_INFO) { + if (UserManager.getMaxSupportedUsers() > 1) { + int deviceOwnerUserId = UserHandle.USER_NULL; + + synchronized (mRestrictionsLock) { + deviceOwnerUserId = mDeviceOwnerUserId; } - data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO, - user.id, - userTypeStandard, - userTypeCustom, - user.flags, - user.creationTime, - user.lastLoggedInTime, - isUserRunningUnlocked - )); + data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO, + UserManager.getMaxSupportedUsers(), + isUserSwitcherEnabled(deviceOwnerUserId))); } + } else { + Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag); + return android.app.StatsManager.PULL_SKIP; } return android.app.StatsManager.PULL_SUCCESS; } @@ -6974,7 +7005,12 @@ public class UserManagerService extends IUserManager.Stub { mUsersOnSecondaryDisplays.put(userId, displayId); } } - } + + @Override + public boolean isUserVisible(int userId, int displayId) { + return isUserVisibleOnDisplay(userId, displayId); + } + } // class LocalService /** * Check if user has restrictions diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index 0a39e64ffcf2..47a3705388b6 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -25,6 +25,7 @@ import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION; @@ -77,6 +78,7 @@ import com.android.server.sdksandbox.SdkSandboxManagerLocal; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; final class VerifyingSession { @@ -353,7 +355,7 @@ final class VerifyingSession { } final int verifierUserId = verifierUser.getIdentifier(); - String[] requiredVerifierPackages = mPm.mRequiredVerifierPackages; + List<String> requiredVerifierPackages = Arrays.asList(mPm.mRequiredVerifierPackages); boolean requiredVerifierPackagesOverridden = false; // Allow verifier override for ADB installations which could already be unverified using @@ -377,8 +379,7 @@ final class VerifyingSession { // are not adding a new way to disable verifications. if (!isAdbVerificationEnabled(pkgLite, verifierUserId, requestedDisableVerification)) { - requiredVerifierPackages = adbVerifierOverridePackages.toArray( - new String[adbVerifierOverridePackages.size()]); + requiredVerifierPackages = adbVerifierOverridePackages; requiredVerifierPackagesOverridden = true; } } @@ -397,6 +398,16 @@ final class VerifyingSession { */ final Computer snapshot = mPm.snapshotComputer(); + final int numRequiredVerifierPackages = requiredVerifierPackages.size(); + for (int i = numRequiredVerifierPackages - 1; i >= 0; i--) { + if (!snapshot.isApplicationEffectivelyEnabled(requiredVerifierPackages.get(i), + SYSTEM_UID)) { + Slog.w(TAG, + "Required verifier: " + requiredVerifierPackages.get(i) + " is disabled"); + requiredVerifierPackages.remove(i); + } + } + for (String requiredVerifierPackage : requiredVerifierPackages) { final int requiredUid = snapshot.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, verifierUserId); @@ -514,7 +525,7 @@ final class VerifyingSession { } } - if (requiredVerifierPackages.length == 0) { + if (requiredVerifierPackages.size() == 0) { Slog.e(TAG, "No required verifiers"); return; } @@ -532,7 +543,7 @@ final class VerifyingSession { final Intent requiredIntent; final String receiverPermission; - if (!requiredVerifierPackagesOverridden || requiredVerifierPackages.length == 1) { + if (!requiredVerifierPackagesOverridden || requiredVerifierPackages.size() == 1) { // Prod code OR test code+single verifier. requiredIntent = new Intent(verification); if (!requiredVerifierPackagesOverridden) { @@ -657,7 +668,7 @@ final class VerifyingSession { * @return true if verification should be performed */ private boolean isVerificationEnabled(PackageInfoLite pkgInfoLite, int userId, - String[] requiredVerifierPackages) { + List<String> requiredVerifierPackages) { if (!DEFAULT_VERIFY_ENABLE) { return false; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 51bf55759962..937a7891a849 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2082,21 +2082,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(long statusBarAnimationStartTime, + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { - return handleTransitionForKeyguardLw(false /* startKeyguardExitAnimation */, - false /* notifyOccluded */); + // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI + // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't + // need to call IKeyguardService#keyguardGoingAway here. + return handleStartTransitionForKeyguardLw(keyguardGoingAway + && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation, + keyguardOccluding, duration); } @Override - public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) { - // When app KEYGUARD_GOING_AWAY or (UN)OCCLUDE app transition is canceled, we need - // to trigger relevant IKeyguardService calls to sync keyguard status in - // WindowManagerService and SysUI. - handleTransitionForKeyguardLw( - keyguardGoingAwayCancelled /* startKeyguardExitAnimation */, - keyguardOccludedCancelled /* notifyOccluded */); + public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { + handleStartTransitionForKeyguardLw( + keyguardGoingAway, false /* keyguardOccludingStarted */, + 0 /* duration */); } }); @@ -3257,44 +3258,35 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onKeyguardOccludedChangedLw(boolean occluded) { - if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing() - && !WindowManagerService.sEnableShellTransitions) { + if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; } else { - setKeyguardOccludedLw(occluded, true /* notify */); + setKeyguardOccludedLw(occluded, false /* force */, + false /* transitionStarted */); } } @Override - public int applyKeyguardOcclusionChange(boolean notify) { + public int applyKeyguardOcclusionChange(boolean transitionStarted) { if (mKeyguardOccludedChanged) { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded=" + mPendingKeyguardOccluded); - if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) { + if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */, + transitionStarted)) { return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER; } } return 0; } - /** - * Called when keyguard related app transition starts, or cancelled. - * - * @param startKeyguardExitAnimation Trigger IKeyguardService#startKeyguardExitAnimation to - * start keyguard exit animation. - * @param notifyOccluded Trigger IKeyguardService#setOccluded binder call to notify whether - * the top activity can occlude the keyguard or not. - * - * @return Whether the flags have changed and we have to redo the layout. - */ - private int handleTransitionForKeyguardLw(boolean startKeyguardExitAnimation, - boolean notifyOccluded) { - final int redoLayout = applyKeyguardOcclusionChange(notifyOccluded); + private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration) { + final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding); if (redoLayout != 0) return redoLayout; - if (startKeyguardExitAnimation) { + if (keyguardGoingAway) { if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation"); - startKeyguardExitAnimation(SystemClock.uptimeMillis()); + startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration); } return 0; } @@ -3526,18 +3518,28 @@ public class PhoneWindowManager implements WindowManagerPolicy { * Updates the occluded state of the Keyguard. * * @param isOccluded Whether the Keyguard is occluded by another window. - * @param notify Notify keyguard occlude status change immediately via - * {@link com.android.internal.policy.IKeyguardService}. + * @param force notify the occluded status to KeyguardService and update flags even though + * occlude status doesn't change. + * @param transitionStarted {@code true} if keyguard (un)occluded transition started. * @return Whether the flags have changed and we have to redo the layout. */ - private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) { + private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force, + boolean transitionStarted) { if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded); mKeyguardOccludedChanged = false; - if (isKeyguardOccluded() == isOccluded) { + if (isKeyguardOccluded() == isOccluded && !force) { return false; } - mKeyguardDelegate.setOccluded(isOccluded, notify); - return mKeyguardDelegate.isShowing(); + + final boolean showing = mKeyguardDelegate.isShowing(); + final boolean animate = showing && !isOccluded; + // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService + // uses remote animation start as a signal to update its occlusion status ,so we don't need + // to notify here. + final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation + || !transitionStarted; + mKeyguardDelegate.setOccluded(isOccluded, animate, notify); + return showing; } /** {@inheritDoc} */ @@ -4933,10 +4935,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void startKeyguardExitAnimation(long startTime) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { if (mKeyguardDelegate != null) { if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation"); - mKeyguardDelegate.startKeyguardExitAnimation(startTime); + mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration); } } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 2b0405073323..4f00992c713e 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -171,10 +171,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void onKeyguardOccludedChangedLw(boolean occluded); /** - * @param notify {@code true} if the status change should be immediately notified via - * {@link com.android.internal.policy.IKeyguardService} + * Applies a keyguard occlusion change if one happened. + * @param transitionStarted Whether keyguard (un)occlude transition is starting or not. */ - int applyKeyguardOcclusionChange(boolean notify); + int applyKeyguardOcclusionChange(boolean transitionStarted); /** * Interface to the Window Manager state associated with a particular @@ -1129,10 +1129,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Notifies the keyguard to start fading out. - * @param startTime the start time of the animation in uptime milliseconds * + * @param startTime the start time of the animation in uptime milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds */ - void startKeyguardExitAnimation(long startTime); + void startKeyguardExitAnimation(long startTime, long fadeoutDuration); /** * Called when System UI has been started. 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 7737421654ee..b79ac6f68be2 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -249,10 +249,10 @@ public class KeyguardServiceDelegate { } } - public void setOccluded(boolean isOccluded, boolean notify) { + public void setOccluded(boolean isOccluded, boolean animate, boolean notify) { if (mKeyguardService != null && notify) { - if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")"); - mKeyguardService.setOccluded(isOccluded, false /* animate */); + if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate); + mKeyguardService.setOccluded(isOccluded, animate); } mKeyguardState.occluded = isOccluded; } @@ -394,9 +394,9 @@ public class KeyguardServiceDelegate { } } - public void startKeyguardExitAnimation(long startTime) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { if (mKeyguardService != null) { - mKeyguardService.startKeyguardExitAnimation(startTime, 0); + mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration); } } diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index df902c2916ba..fe4aa534df6f 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -657,7 +657,7 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat // Now that we have finally received all the data, we can tell mStats about it. synchronized (mStats) { - mStats.recordHistoryEventLocked( + mStats.addHistoryEventLocked( elapsedRealtime, uptime, BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 37643c3c6f6f..202beb377b2c 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -108,7 +108,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsHistory; -import com.android.internal.os.BatteryStatsHistory.HistoryStepDetailsCalculator; import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderTransactionNameResolver; @@ -174,6 +173,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY; private static final boolean DEBUG_BINDER_STATS = false; private static final boolean DEBUG_MEMORY = false; + private static final boolean DEBUG_HISTORY = false; // TODO: remove "tcp" from network methods, since we measure total stats. @@ -322,11 +322,6 @@ public class BatteryStatsImpl extends BatteryStats { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>(); - @NonNull - BatteryStatsHistory copyHistory() { - return mHistory.copy(); - } - @VisibleForTesting public final class UidToRemove { private final int mStartUid; @@ -418,7 +413,7 @@ public class BatteryStatsImpl extends BatteryStats { if (changed) { final long uptimeMs = mClock.uptimeMillis(); final long elapsedRealtimeMs = mClock.elapsedRealtime(); - mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } } @@ -673,16 +668,16 @@ public class BatteryStatsImpl extends BatteryStats { /** * Mapping isolated uids to the actual owning app uid. */ - private final SparseIntArray mIsolatedUids = new SparseIntArray(); + final SparseIntArray mIsolatedUids = new SparseIntArray(); /** * Internal reference count of isolated uids. */ - private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray(); + final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray(); /** * The statistics we have collected organized by uids. */ - private final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>(); + final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>(); // A set of pools of currently active timers. When a timer is queried, we will divide the // elapsed time by the number of active timers to arrive at that timer's share of the time. @@ -690,21 +685,20 @@ public class BatteryStatsImpl extends BatteryStats { // changes. @VisibleForTesting protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>(); - private final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>(); - private final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>(); - private final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = - new SparseArray<>(); - private final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>(); - private final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>(); + final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>(); + final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>(); + final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>(); + final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>(); // Last partial timers we use for distributing CPU usage. @VisibleForTesting @@ -719,24 +713,69 @@ public class BatteryStatsImpl extends BatteryStats { protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(true); private boolean mSystemReady; - private boolean mShuttingDown; - - private final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); - private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator = - new HistoryStepDetailsCalculatorImpl(); - - private boolean mHaveBatteryLevel = false; - private boolean mBatteryPluggedIn; - private int mBatteryStatus; - private int mBatteryLevel; - private int mBatteryPlugType; - private int mBatteryChargeUah; - private int mBatteryHealth; - private int mBatteryTemperature; - private int mBatteryVoltageMv = -1; + boolean mShuttingDown; + + final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); + + long mHistoryBaseTimeMs; + protected boolean mHaveBatteryLevel = false; + protected boolean mRecordingHistory = false; + int mNumHistoryItems; + + private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe; + private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024; + + final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>(); + private SparseArray<HistoryTag> mHistoryTags; + final Parcel mHistoryBuffer = Parcel.obtain(); + final HistoryItem mHistoryLastWritten = new HistoryItem(); + final HistoryItem mHistoryLastLastWritten = new HistoryItem(); + final HistoryItem mHistoryAddTmp = new HistoryItem(); + int mNextHistoryTagIdx = 0; + int mNumHistoryTagChars = 0; + int mHistoryBufferLastPos = -1; + int mActiveHistoryStates = 0xffffffff; + int mActiveHistoryStates2 = 0xffffffff; + long mLastHistoryElapsedRealtimeMs = 0; + long mTrackRunningHistoryElapsedRealtimeMs = 0; + long mTrackRunningHistoryUptimeMs = 0; @NonNull - private final BatteryStatsHistory mHistory; + final BatteryStatsHistory mBatteryStatsHistory; + + final HistoryItem mHistoryCur = new HistoryItem(); + + // Used by computeHistoryStepDetails + HistoryStepDetails mLastHistoryStepDetails = null; + byte mLastHistoryStepLevel = 0; + final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails(); + final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails(); + + /** + * Total time (in milliseconds) spent executing in user code. + */ + long mLastStepCpuUserTimeMs; + long mCurStepCpuUserTimeMs; + /** + * Total time (in milliseconds) spent executing in kernel code. + */ + long mLastStepCpuSystemTimeMs; + long mCurStepCpuSystemTimeMs; + /** + * Times from /proc/stat (but measured in milliseconds). + */ + long mLastStepStatUserTimeMs; + long mLastStepStatSystemTimeMs; + long mLastStepStatIOWaitTimeMs; + long mLastStepStatIrqTimeMs; + long mLastStepStatSoftIrqTimeMs; + long mLastStepStatIdleTimeMs; + long mCurStepStatUserTimeMs; + long mCurStepStatSystemTimeMs; + long mCurStepStatIOWaitTimeMs; + long mCurStepStatIrqTimeMs; + long mCurStepStatSoftIrqTimeMs; + long mCurStepStatIdleTimeMs; private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator; @@ -1352,6 +1391,7 @@ public class BatteryStatsImpl extends BatteryStats { int mDischargeUnplugLevel; int mDischargePlugLevel; int mDischargeCurrentLevel; + int mCurrentBatteryLevel; int mLowDischargeAmountSinceCharge; int mHighDischargeAmountSinceCharge; int mDischargeScreenOnUnplugLevel; @@ -1403,6 +1443,7 @@ public class BatteryStatsImpl extends BatteryStats { private int mNumConnectivityChange; + private int mBatteryVoltageMv = -1; private int mEstimatedBatteryCapacityMah = -1; private int mLastLearnedBatteryCapacityUah = -1; @@ -1586,27 +1627,28 @@ public class BatteryStatsImpl extends BatteryStats { } public BatteryStatsImpl(Clock clock) { - this(clock, null); + this(clock, (File) null); } public BatteryStatsImpl(Clock clock, File historyDirectory) { init(clock); - mHandler = null; - mConstants = new Constants(mHandler); mStartClockTimeMs = clock.currentTimeMillis(); mCheckinFile = null; mDailyFile = null; if (historyDirectory == null) { mStatsFile = null; - mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock); + mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer); } else { mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin")); - mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); + mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, historyDirectory, + this::getMaxHistoryFiles); } + mHandler = null; mPlatformIdleStateCallback = null; mMeasuredEnergyRetriever = null; mUserInfoProvider = null; + mConstants = new Constants(mHandler); + clearHistoryLocked(); } private void init(Clock clock) { @@ -3869,188 +3911,406 @@ public class BatteryStatsImpl extends BatteryStats { return kmt; } - private class HistoryStepDetailsCalculatorImpl implements HistoryStepDetailsCalculator { - private final HistoryStepDetails mDetails = new HistoryStepDetails(); - - private boolean mHasHistoryStepDetails; + /** + * Returns the index for the specified tag. If this is the first time the tag is encountered + * while writing the current history buffer, the method returns + * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code> + */ + private int writeHistoryTag(HistoryTag tag) { + if (tag.string == null) { + Slog.wtfStack(TAG, "writeHistoryTag called with null name"); + } + + final int stringLength = tag.string.length(); + if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) { + Slog.e(TAG, "Long battery history tag: " + tag.string); + tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH); + } + + Integer idxObj = mHistoryTagPool.get(tag); + int idx; + if (idxObj != null) { + idx = idxObj; + if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { + mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG); + } + return idx; + } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) { + idx = mNextHistoryTagIdx; + HistoryTag key = new HistoryTag(); + key.setTo(tag); + tag.poolIdx = idx; + mHistoryTagPool.put(key, idx); + mNextHistoryTagIdx++; + + mNumHistoryTagChars += stringLength + 1; + if (mHistoryTags != null) { + mHistoryTags.put(idx, key); + } + return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG; + } else { + // Tag pool overflow: include the tag itself in the parcel + return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG; + } + } - private int mLastHistoryStepLevel; + /* + The history delta format uses flags to denote further data in subsequent ints in the parcel. + + There is always the first token, which may contain the delta time, or an indicator of + the length of the time (int or long) following this token. + + First token: always present, + 31 23 15 7 0 + █M|L|K|J|I|H|G|F█E|D|C|B|A|T|T|T█T|T|T|T|T|T|T|T█T|T|T|T|T|T|T|T█ + + T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately + follows containing the time, and 0x7ffff indicates a long immediately follows with the + delta time. + A: battery level changed and an int follows with battery data. + B: state changed and an int follows with state change data. + C: state2 has changed and an int follows with state2 change data. + D: wakelock/wakereason has changed and an wakelock/wakereason struct follows. + E: event data has changed and an event struct follows. + F: battery charge in coulombs has changed and an int with the charge follows. + G: state flag denoting that the mobile radio was active. + H: state flag denoting that the wifi radio was active. + I: state flag denoting that a wifi scan occurred. + J: state flag denoting that a wifi full lock was held. + K: state flag denoting that the gps was on. + L: state flag denoting that a wakelock was held. + M: state flag denoting that the cpu was running. + + Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows + with the time delta. + + Battery level int: if A in the first token is set, + 31 23 15 7 0 + █L|L|L|L|L|L|L|T█T|T|T|T|T|T|T|T█T|V|V|V|V|V|V|V█V|V|V|V|V|V|V|D█ + + D: indicates that extra history details follow. + V: the battery voltage. + T: the battery temperature. + L: the battery level (out of 100). + + State change int: if B in the first token is set, + 31 23 15 7 0 + █S|S|S|H|H|H|P|P█F|E|D|C|B| | |A█ | | | | | | | █ | | | | | | | █ + + A: wifi multicast was on. + B: battery was plugged in. + C: screen was on. + D: phone was scanning for signal. + E: audio was on. + F: a sensor was active. + + State2 change int: if C in the first token is set, + 31 23 15 7 0 + █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█ + + A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}. + B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4. + C: a bluetooth scan was active. + D: the camera was active. + E: bluetooth was on. + F: a phone call was active. + G: the device was charging. + H: 2 bits indicating the device-idle (doze) state: off, light, full + I: the flashlight was on. + J: wifi was on. + K: wifi was running. + L: video was playing. + M: power save mode was on. + + Wakelock/wakereason struct: if D in the first token is set, + TODO(adamlesinski): describe wakelock/wakereason struct. + + Event struct: if E in the first token is set, + TODO(adamlesinski): describe the event struct. + + History step details struct: if D in the battery level int is set, + TODO(adamlesinski): describe the history step details struct. + + Battery charge int: if F in the first token is set, an int representing the battery charge + in coulombs follows. + */ - /** - * Total time (in milliseconds) spent executing in user code. - */ - private long mLastStepCpuUserTimeMs; - private long mCurStepCpuUserTimeMs; - /** - * Total time (in milliseconds) spent executing in kernel code. - */ - private long mLastStepCpuSystemTimeMs; - private long mCurStepCpuSystemTimeMs; - /** - * Times from /proc/stat (but measured in milliseconds). - */ - private long mLastStepStatUserTimeMs; - private long mLastStepStatSystemTimeMs; - private long mLastStepStatIOWaitTimeMs; - private long mLastStepStatIrqTimeMs; - private long mLastStepStatSoftIrqTimeMs; - private long mLastStepStatIdleTimeMs; - private long mCurStepStatUserTimeMs; - private long mCurStepStatSystemTimeMs; - private long mCurStepStatIOWaitTimeMs; - private long mCurStepStatIrqTimeMs; - private long mCurStepStatSoftIrqTimeMs; - private long mCurStepStatIdleTimeMs; - - @Override - public HistoryStepDetails getHistoryStepDetails() { - if (mBatteryLevel >= mLastHistoryStepLevel && mHasHistoryStepDetails) { - mLastHistoryStepLevel = mBatteryLevel; - return null; - } + @GuardedBy("this") + public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) { + if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) { + dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS); + cur.writeToParcel(dest, 0); + return; + } - // Perform a CPU update right after we do this collection, so we have started - // collecting good data for the next step. - requestImmediateCpuUpdate(); + final long deltaTime = cur.time - last.time; + final int lastBatteryLevelInt = buildBatteryLevelInt(last); + final int lastStateInt = buildStateInt(last); - if (mPlatformIdleStateCallback != null) { - mDetails.statSubsystemPowerState = - mPlatformIdleStateCallback.getSubsystemLowPowerStats(); - if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" + - mDetails.statSubsystemPowerState); - } - - if (!mHasHistoryStepDetails) { - // We are not generating a delta, so all we need to do is reset the stats - // we will later be doing a delta from. - final int uidCount = mUidStats.size(); - for (int i = 0; i < uidCount; i++) { - final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); - uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; - uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; - } - mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; - mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; - mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; - mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; - mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; - mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; - mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; - mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; - mDetails.clear(); + int deltaTimeToken; + if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) { + deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG; + } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) { + deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT; + } else { + deltaTimeToken = (int)deltaTime; + } + int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK); + final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel + ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0; + final boolean computeStepDetails = includeStepDetails != 0 + || mLastHistoryStepDetails == null; + final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails; + final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt; + if (batteryLevelIntChanged) { + firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG; + } + final int stateInt = buildStateInt(cur); + final boolean stateIntChanged = stateInt != lastStateInt; + if (stateIntChanged) { + firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG; + } + final boolean state2IntChanged = cur.states2 != last.states2; + if (state2IntChanged) { + firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG; + } + if (cur.wakelockTag != null || cur.wakeReasonTag != null) { + firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG; + } + if (cur.eventCode != HistoryItem.EVENT_NONE) { + firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG; + } + + final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah; + if (batteryChargeChanged) { + firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG; + } + dest.writeInt(firstToken); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken) + + " deltaTime=" + deltaTime); + + if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) { + if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) { + if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime); + dest.writeInt((int)deltaTime); } else { - if (DEBUG) { - Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys=" - + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs - + " irq=" + mLastStepStatIrqTimeMs + " sirq=" - + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs); - Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys=" - + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs - + " irq=" + mCurStepStatIrqTimeMs + " sirq=" - + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs); - } - mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs); - mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs); - mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs); - mDetails.statSystemTime = - (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs); - mDetails.statIOWaitTime = - (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs); - mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs); - mDetails.statSoftIrqTime = - (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs); - mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs); - mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1; - mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0; - mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0; - final int uidCount = mUidStats.size(); - for (int i = 0; i < uidCount; i++) { - final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); - final int totalUTimeMs = - (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs); - final int totalSTimeMs = - (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs); - final int totalTimeMs = totalUTimeMs + totalSTimeMs; - uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; - uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; - if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) { - continue; - } - if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) { - mDetails.appCpuUid3 = uid.mUid; - mDetails.appCpuUTime3 = totalUTimeMs; - mDetails.appCpuSTime3 = totalSTimeMs; - } else { - mDetails.appCpuUid3 = mDetails.appCpuUid2; - mDetails.appCpuUTime3 = mDetails.appCpuUTime2; - mDetails.appCpuSTime3 = mDetails.appCpuSTime2; - if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) { - mDetails.appCpuUid2 = uid.mUid; - mDetails.appCpuUTime2 = totalUTimeMs; - mDetails.appCpuSTime2 = totalSTimeMs; - } else { - mDetails.appCpuUid2 = mDetails.appCpuUid1; - mDetails.appCpuUTime2 = mDetails.appCpuUTime1; - mDetails.appCpuSTime2 = mDetails.appCpuSTime1; - mDetails.appCpuUid1 = uid.mUid; - mDetails.appCpuUTime1 = totalUTimeMs; - mDetails.appCpuSTime1 = totalSTimeMs; - } - } - } - mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; - mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; - mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; - mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; - mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; - mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; - mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; - mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; + if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime); + dest.writeLong(deltaTime); + } + } + if (batteryLevelIntChanged) { + dest.writeInt(batteryLevelInt); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x" + + Integer.toHexString(batteryLevelInt) + + " batteryLevel=" + cur.batteryLevel + + " batteryTemp=" + cur.batteryTemperature + + " batteryVolt=" + (int)cur.batteryVoltage); + } + if (stateIntChanged) { + dest.writeInt(stateInt); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x" + + Integer.toHexString(stateInt) + + " batteryStatus=" + cur.batteryStatus + + " batteryHealth=" + cur.batteryHealth + + " batteryPlugType=" + cur.batteryPlugType + + " states=0x" + Integer.toHexString(cur.states)); + } + if (state2IntChanged) { + dest.writeInt(cur.states2); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x" + + Integer.toHexString(cur.states2)); + } + if (cur.wakelockTag != null || cur.wakeReasonTag != null) { + int wakeLockIndex; + int wakeReasonIndex; + if (cur.wakelockTag != null) { + wakeLockIndex = writeHistoryTag(cur.wakelockTag); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx + + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string); + } else { + wakeLockIndex = 0xffff; + } + if (cur.wakeReasonTag != null) { + wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx + + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string); + } else { + wakeReasonIndex = 0xffff; + } + dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex); + if (cur.wakelockTag != null + && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { + cur.wakelockTag.writeToParcel(dest, 0); + cur.tagsFirstOccurrence = true; + } + if (cur.wakeReasonTag != null + && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { + cur.wakeReasonTag.writeToParcel(dest, 0); + cur.tagsFirstOccurrence = true; } - - mHasHistoryStepDetails = mBatteryLevel <= mLastHistoryStepLevel; - mLastHistoryStepLevel = mBatteryLevel; - - return mDetails; } - - public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs, - int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs, - int statSoftIrqTimeMs, int statIdleTimeMs) { - if (DEBUG) { - Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs - + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs - + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs - + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs); + if (cur.eventCode != HistoryItem.EVENT_NONE) { + final int index = writeHistoryTag(cur.eventTag); + final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16); + dest.writeInt(codeAndIndex); + if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) { + cur.eventTag.writeToParcel(dest, 0); + cur.tagsFirstOccurrence = true; } - mCurStepCpuUserTimeMs += totalUTimeMs; - mCurStepCpuSystemTimeMs += totalSTimeMs; - mCurStepStatUserTimeMs += statUserTimeMs; - mCurStepStatSystemTimeMs += statSystemTimeMs; - mCurStepStatIOWaitTimeMs += statIOWaitTimeMs; - mCurStepStatIrqTimeMs += statIrqTimeMs; - mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs; - mCurStepStatIdleTimeMs += statIdleTimeMs; + if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#" + + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":" + + cur.eventTag.string); } + if (computeStepDetails) { + if (mPlatformIdleStateCallback != null) { + mCurHistoryStepDetails.statSubsystemPowerState = + mPlatformIdleStateCallback.getSubsystemLowPowerStats(); + if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" + + mCurHistoryStepDetails.statSubsystemPowerState); - @Override - public void clear() { - mHasHistoryStepDetails = false; - mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0; - mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0; - mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0; - mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0; - mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0; - mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0; - mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0; - mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0; + } + computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails); + if (includeStepDetails != 0) { + mCurHistoryStepDetails.writeToParcel(dest); + } + cur.stepDetails = mCurHistoryStepDetails; + mLastHistoryStepDetails = mCurHistoryStepDetails; + } else { + cur.stepDetails = null; + } + if (mLastHistoryStepLevel < cur.batteryLevel) { + mLastHistoryStepDetails = null; + } + mLastHistoryStepLevel = cur.batteryLevel; + + if (batteryChargeChanged) { + if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah); + dest.writeInt(cur.batteryChargeUah); + } + dest.writeDouble(cur.modemRailChargeMah); + dest.writeDouble(cur.wifiRailChargeMah); + } + + private int buildBatteryLevelInt(HistoryItem h) { + return ((((int)h.batteryLevel)<<25)&0xfe000000) + | ((((int)h.batteryTemperature)<<15)&0x01ff8000) + | ((((int)h.batteryVoltage)<<1)&0x00007ffe); + } + + private int buildStateInt(HistoryItem h) { + int plugType = 0; + if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) { + plugType = 1; + } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) { + plugType = 2; + } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) { + plugType = 3; + } + return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK) + << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT) + | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK) + << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT) + | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK) + << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT) + | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK)); + } + + private void computeHistoryStepDetails(final HistoryStepDetails out, + final HistoryStepDetails last) { + final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out; + + // Perform a CPU update right after we do this collection, so we have started + // collecting good data for the next step. + requestImmediateCpuUpdate(); + + if (last == null) { + // We are not generating a delta, so all we need to do is reset the stats + // we will later be doing a delta from. + final int NU = mUidStats.size(); + for (int i=0; i<NU; i++) { + final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); + uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; + uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; + } + mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; + mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; + mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; + mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; + mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; + mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; + mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; + mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; + tmp.clear(); + return; + } + if (DEBUG) { + Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys=" + + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs + + " irq=" + mLastStepStatIrqTimeMs + " sirq=" + + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs); + Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys=" + + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs + + " irq=" + mCurStepStatIrqTimeMs + " sirq=" + + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs); + } + out.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs); + out.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs); + out.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs); + out.statSystemTime = (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs); + out.statIOWaitTime = (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs); + out.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs); + out.statSoftIrqTime = (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs); + out.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs); + out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1; + out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0; + out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0; + final int NU = mUidStats.size(); + for (int i=0; i<NU; i++) { + final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); + final int totalUTimeMs = (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs); + final int totalSTimeMs = (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs); + final int totalTimeMs = totalUTimeMs + totalSTimeMs; + uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; + uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; + if (totalTimeMs <= (out.appCpuUTime3 + out.appCpuSTime3)) { + continue; + } + if (totalTimeMs <= (out.appCpuUTime2 + out.appCpuSTime2)) { + out.appCpuUid3 = uid.mUid; + out.appCpuUTime3 = totalUTimeMs; + out.appCpuSTime3 = totalSTimeMs; + } else { + out.appCpuUid3 = out.appCpuUid2; + out.appCpuUTime3 = out.appCpuUTime2; + out.appCpuSTime3 = out.appCpuSTime2; + if (totalTimeMs <= (out.appCpuUTime1 + out.appCpuSTime1)) { + out.appCpuUid2 = uid.mUid; + out.appCpuUTime2 = totalUTimeMs; + out.appCpuSTime2 = totalSTimeMs; + } else { + out.appCpuUid2 = out.appCpuUid1; + out.appCpuUTime2 = out.appCpuUTime1; + out.appCpuSTime2 = out.appCpuSTime1; + out.appCpuUid1 = uid.mUid; + out.appCpuUTime1 = totalUTimeMs; + out.appCpuSTime1 = totalSTimeMs; + } + } } + mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; + mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; + mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; + mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; + mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; + mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; + mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; + mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; } @GuardedBy("this") @Override public void commitCurrentHistoryBatchLocked() { - mHistory.commitCurrentHistoryBatchLocked(); + mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; } @GuardedBy("this") @@ -4066,9 +4326,191 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - public void recordHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, + void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { + if (!mHaveBatteryLevel || !mRecordingHistory) { + return; + } + + final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time; + final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates); + final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2); + final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states; + final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2; + if (DEBUG) { + Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff=" + + Integer.toHexString(diffStates) + " lastDiff=" + + Integer.toHexString(lastDiffStates) + " diff2=" + + Integer.toHexString(diffStates2) + " lastDiff2=" + + Integer.toHexString(lastDiffStates2)); + } + if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE + && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0 + && (diffStates2&lastDiffStates2) == 0 + && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence) + && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null) + && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null) + && mHistoryLastWritten.stepDetails == null + && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE + || cur.eventCode == HistoryItem.EVENT_NONE) + && mHistoryLastWritten.batteryLevel == cur.batteryLevel + && mHistoryLastWritten.batteryStatus == cur.batteryStatus + && mHistoryLastWritten.batteryHealth == cur.batteryHealth + && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType + && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature + && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) { + // We can merge this new change in with the last one. Merging is + // allowed as long as only the states have changed, and within those states + // as long as no bit has changed both between now and the last entry, as + // well as the last entry and the one before it (so we capture any toggles). + if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos); + mHistoryBuffer.setDataSize(mHistoryBufferLastPos); + mHistoryBuffer.setDataPosition(mHistoryBufferLastPos); + mHistoryBufferLastPos = -1; + elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs; + // If the last written history had a wakelock tag, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have a wakelock tag. + if (mHistoryLastWritten.wakelockTag != null) { + cur.wakelockTag = cur.localWakelockTag; + cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag); + } + // If the last written history had a wake reason tag, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have a wakelock tag. + if (mHistoryLastWritten.wakeReasonTag != null) { + cur.wakeReasonTag = cur.localWakeReasonTag; + cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag); + } + // If the last written history had an event, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have an event. + if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) { + cur.eventCode = mHistoryLastWritten.eventCode; + cur.eventTag = cur.localEventTag; + cur.eventTag.setTo(mHistoryLastWritten.eventTag); + } + mHistoryLastWritten.setTo(mHistoryLastLastWritten); + } + final int dataSize = mHistoryBuffer.dataSize(); + + if (dataSize >= mConstants.MAX_HISTORY_BUFFER) { + //open a new history file. + final long start = SystemClock.uptimeMillis(); + writeHistoryLocked(); + if (DEBUG) { + Slog.d(TAG, "addHistoryBufferLocked writeHistoryLocked takes ms:" + + (SystemClock.uptimeMillis() - start)); + } + mBatteryStatsHistory.startNextFile(); + mHistoryBuffer.setDataSize(0); + mHistoryBuffer.setDataPosition(0); + mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2); + mHistoryBufferLastPos = -1; + mHistoryLastWritten.clear(); + mHistoryLastLastWritten.clear(); + + // Mark every entry in the pool with a flag indicating that the tag + // has not yet been encountered while writing the current history buffer. + for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) { + entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG); + } + // Make a copy of mHistoryCur. + HistoryItem copy = new HistoryItem(); + copy.setTo(cur); + // startRecordingHistory will reset mHistoryCur. + startRecordingHistory(elapsedRealtimeMs, uptimeMs, false); + // Add the copy into history buffer. + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, copy); + return; + } + + if (dataSize == 0) { + // The history is currently empty; we need it to start with a time stamp. + cur.currentTime = mClock.currentTimeMillis(); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur); + } + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); + } + + @GuardedBy("this") + private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) { + if (mBatteryStatsHistoryIterator != null) { + throw new IllegalStateException("Can't do this while iterating history!"); + } + mHistoryBufferLastPos = mHistoryBuffer.dataPosition(); + mHistoryLastLastWritten.setTo(mHistoryLastWritten); + final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence; + mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur); + mHistoryLastWritten.tagsFirstOccurrence = hasTags; + mHistoryLastWritten.states &= mActiveHistoryStates; + mHistoryLastWritten.states2 &= mActiveHistoryStates2; + writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); + mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; + cur.wakelockTag = null; + cur.wakeReasonTag = null; + cur.eventCode = HistoryItem.EVENT_NONE; + cur.eventTag = null; + cur.tagsFirstOccurrence = false; + if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos + + " now " + mHistoryBuffer.dataPosition() + + " size is now " + mHistoryBuffer.dataSize()); + } + + @GuardedBy("this") + void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) { + if (mTrackRunningHistoryElapsedRealtimeMs != 0) { + final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs; + final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs; + if (diffUptimeMs < (diffElapsedMs - 20)) { + final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs); + mHistoryAddTmp.setTo(mHistoryLastWritten); + mHistoryAddTmp.wakelockTag = null; + mHistoryAddTmp.wakeReasonTag = null; + mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE; + mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG; + addHistoryRecordInnerLocked(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp); + } + } + mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG; + mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs; + mTrackRunningHistoryUptimeMs = uptimeMs; + addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur); + } + + @GuardedBy("this") + void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur); + } + + @GuardedBy("this") + public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, String name, int uid) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid); + mHistoryCur.eventCode = code; + mHistoryCur.eventTag = mHistoryCur.localEventTag; + mHistoryCur.eventTag.string = name; + mHistoryCur.eventTag.uid = uid; + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + } + + @GuardedBy("this") + void clearHistoryLocked() { + if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!"); + mHistoryBaseTimeMs = 0; + mLastHistoryElapsedRealtimeMs = 0; + mTrackRunningHistoryElapsedRealtimeMs = 0; + mTrackRunningHistoryUptimeMs = 0; + + mHistoryBuffer.setDataSize(0); + mHistoryBuffer.setDataPosition(0); + mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2); + mHistoryLastLastWritten.clear(); + mHistoryLastWritten.clear(); + mHistoryTagPool.clear(); + mNextHistoryTagIdx = 0; + mNumHistoryTagChars = 0; + mHistoryBufferLastPos = -1; + mActiveHistoryStates = 0xffffffff; + mActiveHistoryStates2 = 0xffffffff; } @GuardedBy("this") @@ -4221,13 +4663,13 @@ public class BatteryStatsImpl extends BatteryStats { if (!mActiveEvents.updateState(code, name, uid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, code, name, uid); } @GuardedBy("this") public void noteCurrentTimeChangedLocked(long currentTimeMs, long elapsedRealtimeMs, long uptimeMs) { - mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs); + recordCurrentTimeChangeLocked(currentTimeMs, elapsedRealtimeMs, uptimeMs); } @GuardedBy("this") @@ -4244,7 +4686,7 @@ public class BatteryStatsImpl extends BatteryStats { if (!mRecordAllHistory) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid); } @GuardedBy("this") @@ -4302,7 +4744,8 @@ public class BatteryStatsImpl extends BatteryStats { if (!mRecordAllHistory) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH, + name, uid); } @GuardedBy("this") @@ -4318,7 +4761,7 @@ public class BatteryStatsImpl extends BatteryStats { if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid); } @GuardedBy("this") @@ -4334,7 +4777,8 @@ public class BatteryStatsImpl extends BatteryStats { if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH, + name, uid); } @GuardedBy("this") @@ -4350,7 +4794,7 @@ public class BatteryStatsImpl extends BatteryStats { if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid); } @GuardedBy("this") @@ -4368,7 +4812,7 @@ public class BatteryStatsImpl extends BatteryStats { if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid); } @GuardedBy("this") @@ -4416,7 +4860,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i = 0; i < workSource.size(); ++i) { uid = mapUid(workSource.getUid(i)); if (mActiveEvents.updateState(historyItem, name, uid, 0)) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid); } } @@ -4425,7 +4869,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i = 0; i < workChains.size(); ++i) { uid = mapUid(workChains.get(i).getAttributionUid()); if (mActiveEvents.updateState(historyItem, name, uid, 0)) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid); } } } @@ -4433,7 +4877,7 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (mActiveEvents.updateState(historyItem, name, uid, 0)) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid); } } } @@ -4508,7 +4952,7 @@ public class BatteryStatsImpl extends BatteryStats { for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) { SparseIntArray uids = ent.getValue(); for (int j=0; j<uids.size(); j++) { - mHistory.recordEvent(mSecRealtime, mSecUptime, + addHistoryEventLocked(mSecRealtime, mSecUptime, HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j)); } } @@ -4523,8 +4967,8 @@ public class BatteryStatsImpl extends BatteryStats { for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) { SparseIntArray uids = ent.getValue(); for (int j=0; j<uids.size(); j++) { - mHistory.recordEvent(mSecRealtime, mSecUptime, HistoryItem.EVENT_PROC_START, - ent.getKey(), uids.keyAt(j)); + addHistoryEventLocked(mSecRealtime, mSecUptime, + HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j)); } } } @@ -4567,19 +5011,30 @@ public class BatteryStatsImpl extends BatteryStats { if (mRecordAllHistory) { if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid, 0)) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid); } } if (mWakeLockNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: " + + Integer.toHexString(mHistoryCur.states)); + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.string = historyName; + mHistoryCur.wakelockTag.uid = mappedUid; mWakeLockImportant = !unimportantForLogging; - mHistory.recordWakelockStartEvent(elapsedRealtimeMs, uptimeMs, historyName, - mappedUid); - } else if (!mWakeLockImportant && !unimportantForLogging) { - if (mHistory.maybeUpdateWakelockTag(elapsedRealtimeMs, uptimeMs, historyName, - mappedUid)) { - mWakeLockImportant = true; - } + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + } else if (!mWakeLockImportant && !unimportantForLogging + && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) { + if (mHistoryLastWritten.wakelockTag != null) { + // We'll try to update the last tag. + mHistoryLastWritten.wakelockTag = null; + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.string = historyName; + mHistoryCur.wakelockTag.uid = mappedUid; + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + } + mWakeLockImportant = true; } mWakeLockNesting++; } @@ -4632,13 +5087,15 @@ public class BatteryStatsImpl extends BatteryStats { } if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid, 0)) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid); } } if (mWakeLockNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WAKE_LOCK_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } if (mappedUid >= 0) { @@ -4829,7 +5286,7 @@ public class BatteryStatsImpl extends BatteryStats { mappedUid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START, historyName, mappedUid); if (mappedUid != uid) { // Prevent the isolated uid mapping from being removed while the wakelock is @@ -4882,7 +5339,7 @@ public class BatteryStatsImpl extends BatteryStats { mappedUid, 0)) { return; } - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH, historyName, mappedUid); if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. @@ -4904,10 +5361,15 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) { + if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": " + + Integer.toHexString(mHistoryCur.states)); aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs); - mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason); + mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag; + mHistoryCur.wakeReasonTag.string = reason; + mHistoryCur.wakeReasonTag.uid = 0; mLastWakeupReason = reason; mLastWakeupUptimeMs = uptimeMs; + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } @GuardedBy("this") @@ -4918,11 +5380,22 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs, - int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs, - int statSoftIrqTimeMs, int statIdleTimeMs) { - mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs, - statSystemTimeMs, statIOWaitTimeMs, statIrqTimeMs, - statSoftIrqTimeMs, statIdleTimeMs); + int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs, + int statSoftIrqTimeMs, int statIdleTimeMs) { + if (DEBUG) { + Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs + + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs + + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs + + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs); + } + mCurStepCpuUserTimeMs += totalUTimeMs; + mCurStepCpuSystemTimeMs += totalSTimeMs; + mCurStepStatUserTimeMs += statUserTimeMs; + mCurStepStatSystemTimeMs += statSystemTimeMs; + mCurStepStatIOWaitTimeMs += statIOWaitTimeMs; + mCurStepStatIrqTimeMs += statIrqTimeMs; + mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs; + mCurStepStatIdleTimeMs += statIdleTimeMs; } public void noteProcessDiedLocked(int uid, int pid) { @@ -4952,8 +5425,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) { uid = mapUid(uid); if (mSensorNesting == 0) { - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_SENSOR_ON_FLAG); + mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } mSensorNesting++; getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -4970,8 +5445,10 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); mSensorNesting--; if (mSensorNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_SENSOR_ON_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) .noteStopSensor(sensor, elapsedRealtimeMs); @@ -5021,8 +5498,10 @@ public class BatteryStatsImpl extends BatteryStats { } final int mappedUid = mapUid(uid); if (mGpsNesting == 0) { - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_GPS_ON_FLAG); + mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } mGpsNesting++; @@ -5047,8 +5526,10 @@ public class BatteryStatsImpl extends BatteryStats { final int mappedUid = mapUid(uid); mGpsNesting--; if (mGpsNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_GPS_ON_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs); mGpsSignalQualityBin = -1; } @@ -5081,9 +5562,12 @@ public class BatteryStatsImpl extends BatteryStats { if(!mGpsSignalQualityTimer[signalLevel].isRunningLocked()) { mGpsSignalQualityTimer[signalLevel].startRunningLocked(elapsedRealtimeMs); } - mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs, signalLevel); + mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK) + | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mGpsSignalQualityBin = signalLevel; } + return; } @GuardedBy("this") @@ -5256,33 +5740,41 @@ public class BatteryStatsImpl extends BatteryStats { } } - int startStates = 0; - int stopStates = 0; + boolean updateHistory = false; if (Display.isDozeState(state) && !Display.isDozeState(oldState)) { - startStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG; + mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG; mScreenDozeTimer.startRunningLocked(elapsedRealtimeMs); + updateHistory = true; } else if (Display.isDozeState(oldState) && !Display.isDozeState(state)) { - stopStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG; + mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG; mScreenDozeTimer.stopRunningLocked(elapsedRealtimeMs); + updateHistory = true; } if (Display.isOnState(state)) { - startStates |= HistoryItem.STATE_SCREEN_ON_FLAG; + mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: " + + Integer.toHexString(mHistoryCur.states)); mScreenOnTimer.startRunningLocked(elapsedRealtimeMs); if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin] .startRunningLocked(elapsedRealtimeMs); } + updateHistory = true; } else if (Display.isOnState(oldState)) { - stopStates |= HistoryItem.STATE_SCREEN_ON_FLAG; + mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: " + + Integer.toHexString(mHistoryCur.states)); mScreenOnTimer.stopRunningLocked(elapsedRealtimeMs); if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin] .stopRunningLocked(elapsedRealtimeMs); } + updateHistory = true; } - if (startStates != 0 || stopStates != 0) { - mHistory.recordStateChangeEvent(elapsedRealtimeMs, uptimeMs, startStates, - stopStates); + if (updateHistory) { + if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: " + + Display.stateToString(state)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } // Per screen state Cpu stats needed. Prepare to schedule an external sync. @@ -5396,7 +5888,13 @@ public class BatteryStatsImpl extends BatteryStats { long uptimeMs) { if (mScreenBrightnessBin != overallBin) { if (overallBin >= 0) { - mHistory.recordScreenBrightnessEvent(elapsedRealtimeMs, uptimeMs, overallBin); + mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK) + | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT); + if (DEBUG_HISTORY) { + Slog.v(TAG, "Screen brightness " + overallBin + " to: " + + Integer.toHexString(mHistoryCur.states)); + } + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } if (mScreenState == Display.STATE_ON) { if (mScreenBrightnessBin >= 0) { @@ -5424,8 +5922,8 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWakeUpLocked(String reason, int reasonUid, long elapsedRealtimeMs, long uptimeMs) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP, reason, - reasonUid); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP, + reason, reasonUid); } @GuardedBy("this") @@ -5444,7 +5942,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteConnectivityChangedLocked(int type, String extra, long elapsedRealtimeMs, long uptimeMs) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED, extra, type); mNumConnectivityChange++; } @@ -5453,7 +5951,7 @@ public class BatteryStatsImpl extends BatteryStats { private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis, final long uptimeMillis, int uid) { uid = mapUid(uid); - mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "", + addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "", uid); getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteMobileRadioApWakeupLocked(); } @@ -5479,8 +5977,7 @@ public class BatteryStatsImpl extends BatteryStats { } mMobileRadioActiveStartTimeMs = realElapsedRealtimeMs = timestampNs / (1000 * 1000); - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG); + mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; } else { realElapsedRealtimeMs = timestampNs / (1000*1000); long lastUpdateTimeMs = mMobileRadioActiveStartTimeMs; @@ -5492,9 +5989,11 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtimeMs - realElapsedRealtimeMs); } - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; } + if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mMobileRadioPowerState = powerState; // Inform current RatBatteryStats that the modem active state might have changed. @@ -5544,14 +6043,17 @@ public class BatteryStatsImpl extends BatteryStats { mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState; mPowerSaveModeEnabled = enabled; if (enabled) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_POWER_SAVE_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: " + + Integer.toHexString(mHistoryCur.states2)); mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtimeMs); } else { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_POWER_SAVE_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: " + + Integer.toHexString(mHistoryCur.states2)); mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtimeMs); } + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON @@ -5575,7 +6077,7 @@ public class BatteryStatsImpl extends BatteryStats { nowLightIdling = true; } if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE, activeReason, activeUid); } if (mDeviceIdling != nowIdling || mDeviceLightIdling != nowLightIdling) { @@ -5605,7 +6107,11 @@ public class BatteryStatsImpl extends BatteryStats { } } if (mDeviceIdleMode != mode) { - mHistory.recordDeviceIdleEvent(elapsedRealtimeMs, uptimeMs, mode); + mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK) + | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); long lastDuration = elapsedRealtimeMs - mLastIdleTimeStartMs; mLastIdleTimeStartMs = elapsedRealtimeMs; if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { @@ -5633,7 +6139,7 @@ public class BatteryStatsImpl extends BatteryStats { public void notePackageInstalledLocked(String pkgName, long versionCode, long elapsedRealtimeMs, long uptimeMs) { // XXX need to figure out what to do with long version codes. - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED, + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED, pkgName, (int)versionCode); PackageChange pc = new PackageChange(); pc.mPackageName = pkgName; @@ -5645,8 +6151,8 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void notePackageUninstalledLocked(String pkgName, long elapsedRealtimeMs, long uptimeMs) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_UNINSTALLED, - pkgName, 0); + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, + HistoryItem.EVENT_PACKAGE_UNINSTALLED, pkgName, 0); PackageChange pc = new PackageChange(); pc.mPackageName = pkgName; pc.mUpdate = true; @@ -5675,8 +6181,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void notePhoneOnLocked(long elapsedRealtimeMs, long uptimeMs) { if (!mPhoneOn) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_PHONE_IN_CALL_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mPhoneOn = true; mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs); } @@ -5685,8 +6193,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void notePhoneOffLocked(long elapsedRealtimeMs, long uptimeMs) { if (mPhoneOn) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_PHONE_IN_CALL_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mPhoneOn = false; mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs); } @@ -5724,12 +6234,11 @@ public class BatteryStatsImpl extends BatteryStats { if (mUsbDataState != newState) { mUsbDataState = newState; if (connected) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_USB_DATA_LINK_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_USB_DATA_LINK_FLAG; } else { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_USB_DATA_LINK_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_USB_DATA_LINK_FLAG; } + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } @@ -5750,10 +6259,6 @@ public class BatteryStatsImpl extends BatteryStats { long elapsedRealtimeMs, long uptimeMs) { boolean scanning = false; boolean newHistory = false; - int addStateFlag = 0; - int removeStateFlag = 0; - int newState = -1; - int newSignalStrength = -1; mPhoneServiceStateRaw = state; mPhoneSimStateRaw = simState; @@ -5782,8 +6287,10 @@ public class BatteryStatsImpl extends BatteryStats { scanning = true; strengthBin = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (!mPhoneSignalScanningTimer.isRunningLocked()) { - addStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG; + mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG; newHistory = true; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: " + + Integer.toHexString(mHistoryCur.states)); mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtimeMs); FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state, simState, strengthBin); @@ -5793,7 +6300,9 @@ public class BatteryStatsImpl extends BatteryStats { if (!scanning) { // If we are no longer scanning, then stop the scanning timer. if (mPhoneSignalScanningTimer.isRunningLocked()) { - removeStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG; + mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: " + + Integer.toHexString(mHistoryCur.states)); newHistory = true; mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtimeMs); FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state, @@ -5802,7 +6311,10 @@ public class BatteryStatsImpl extends BatteryStats { } if (mPhoneServiceState != state) { - newState = state; + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK) + | (state << HistoryItem.STATE_PHONE_STATE_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + state + " to: " + + Integer.toHexString(mHistoryCur.states)); newHistory = true; mPhoneServiceState = state; } @@ -5816,7 +6328,11 @@ public class BatteryStatsImpl extends BatteryStats { if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) { mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs); } - newSignalStrength = strengthBin; + mHistoryCur.states = + (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK) + | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: " + + Integer.toHexString(mHistoryCur.states)); newHistory = true; FrameworkStatsLog.write( FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin); @@ -5827,8 +6343,7 @@ public class BatteryStatsImpl extends BatteryStats { } if (newHistory) { - mHistory.recordPhoneStateChangeEvent(elapsedRealtimeMs, uptimeMs, - addStateFlag, removeStateFlag, newState, newSignalStrength); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } @@ -5952,7 +6467,11 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { - mHistory.recordDataConnectionTypeChangeEvent(elapsedRealtimeMs, uptimeMs, bin); + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK) + | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); if (mPhoneDataConnectionType >= 0) { mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked( elapsedRealtimeMs); @@ -6025,8 +6544,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) { if (!mWifiOn) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_WIFI_ON_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mWifiOn = true; mWifiOnTimer.startRunningLocked(elapsedRealtimeMs); scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI); @@ -6036,8 +6557,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWifiOffLocked(long elapsedRealtimeMs, long uptimeMs) { if (mWifiOn) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_WIFI_ON_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mWifiOn = false; mWifiOnTimer.stopRunningLocked(elapsedRealtimeMs); scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI); @@ -6048,8 +6571,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteAudioOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { uid = mapUid(uid); if (mAudioOnNesting == 0) { - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_AUDIO_ON_FLAG); + mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mAudioOnTimer.startRunningLocked(elapsedRealtimeMs); } mAudioOnNesting++; @@ -6064,8 +6589,10 @@ public class BatteryStatsImpl extends BatteryStats { } uid = mapUid(uid); if (--mAudioOnNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_AUDIO_ON_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mAudioOnTimer.stopRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6076,8 +6603,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteVideoOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { uid = mapUid(uid); if (mVideoOnNesting == 0) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_VIDEO_ON_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mVideoOnTimer.startRunningLocked(elapsedRealtimeMs); } mVideoOnNesting++; @@ -6092,8 +6621,10 @@ public class BatteryStatsImpl extends BatteryStats { } uid = mapUid(uid); if (--mVideoOnNesting == 0) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_VIDEO_ON_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mVideoOnTimer.stopRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6104,8 +6635,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetAudioLocked(long elapsedRealtimeMs, long uptimeMs) { if (mAudioOnNesting > 0) { mAudioOnNesting = 0; - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_AUDIO_ON_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mAudioOnTimer.stopAllRunningLocked(elapsedRealtimeMs); for (int i=0; i<mUidStats.size(); i++) { BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); @@ -6118,8 +6651,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetVideoLocked(long elapsedRealtimeMs, long uptimeMs) { if (mVideoOnNesting > 0) { mVideoOnNesting = 0; - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_VIDEO_ON_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mVideoOnTimer.stopAllRunningLocked(elapsedRealtimeMs); for (int i=0; i<mUidStats.size(); i++) { BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); @@ -6171,8 +6706,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFlashlightOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { uid = mapUid(uid); if (mFlashlightOnNesting++ == 0) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_FLASHLIGHT_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_FLASHLIGHT_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight on to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mFlashlightOnTimer.startRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6186,8 +6723,10 @@ public class BatteryStatsImpl extends BatteryStats { } uid = mapUid(uid); if (--mFlashlightOnNesting == 0) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_FLASHLIGHT_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mFlashlightOnTimer.stopRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6198,8 +6737,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteCameraOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { uid = mapUid(uid); if (mCameraOnNesting++ == 0) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_CAMERA_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_CAMERA_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Camera on to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mCameraOnTimer.startRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6213,8 +6754,10 @@ public class BatteryStatsImpl extends BatteryStats { } uid = mapUid(uid); if (--mCameraOnNesting == 0) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_CAMERA_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6225,8 +6768,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetCameraLocked(long elapsedRealtimeMs, long uptimeMs) { if (mCameraOnNesting > 0) { mCameraOnNesting = 0; - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_CAMERA_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mCameraOnTimer.stopAllRunningLocked(elapsedRealtimeMs); for (int i=0; i<mUidStats.size(); i++) { BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); @@ -6239,8 +6784,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetFlashlightLocked(long elapsedRealtimeMs, long uptimeMs) { if (mFlashlightOnNesting > 0) { mFlashlightOnNesting = 0; - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_FLASHLIGHT_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mFlashlightOnTimer.stopAllRunningLocked(elapsedRealtimeMs); for (int i=0; i<mUidStats.size(); i++) { BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); @@ -6257,8 +6804,10 @@ public class BatteryStatsImpl extends BatteryStats { } uid = mapUid(uid); if (mBluetoothScanNesting == 0) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mBluetoothScanTimer.startRunningLocked(elapsedRealtimeMs); } mBluetoothScanNesting++; @@ -6299,8 +6848,10 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); mBluetoothScanNesting--; if (mBluetoothScanNesting == 0) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan stopped for: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6335,8 +6886,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetBluetoothScanLocked(long elapsedRealtimeMs, long uptimeMs) { if (mBluetoothScanNesting > 0) { mBluetoothScanNesting = 0; - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs); for (int i=0; i<mUidStats.size(); i++) { BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); @@ -6376,7 +6929,7 @@ public class BatteryStatsImpl extends BatteryStats { private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis, final long uptimeMillis, int uid) { uid = mapUid(uid); - mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "", + addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "", uid); getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteWifiRadioApWakeupLocked(); } @@ -6392,14 +6945,15 @@ public class BatteryStatsImpl extends BatteryStats { if (uid > 0) { noteWifiRadioApWakeupLocked(elapsedRealtimeMs, uptimeMs, uid); } - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG); + mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; mWifiActiveTimer.startRunningLocked(elapsedRealtimeMs); } else { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; mWifiActiveTimer.stopRunningLocked(timestampNs / (1000 * 1000)); } + if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mWifiRadioPowerState = powerState; } } @@ -6407,8 +6961,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWifiRunningLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) { if (!mGlobalWifiRunning) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_WIFI_RUNNING_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mGlobalWifiRunning = true; mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtimeMs); int N = ws.size(); @@ -6476,8 +7032,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWifiStoppedLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) { if (mGlobalWifiRunning) { - mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_WIFI_RUNNING_FLAG); + mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mGlobalWifiRunning = false; mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs); int N = ws.size(); @@ -6525,7 +7083,12 @@ public class BatteryStatsImpl extends BatteryStats { } mWifiSupplState = supplState; mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtimeMs); - mHistory.recordWifiSupplicantStateChangeEvent(elapsedRealtimeMs, uptimeMs, supplState); + mHistoryCur.states2 = + (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK) + | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } @@ -6554,8 +7117,12 @@ public class BatteryStatsImpl extends BatteryStats { if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) { mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs); } - mHistory.recordWifiSignalStrengthChangeEvent(elapsedRealtimeMs, uptimeMs, - strengthBin); + mHistoryCur.states2 = + (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK) + | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } else { stopAllWifiSignalStrengthTimersLocked(-1, elapsedRealtimeMs); } @@ -6568,8 +7135,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteFullWifiLockAcquiredLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { if (mWifiFullLockNesting == 0) { - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_FULL_LOCK_FLAG); + mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } mWifiFullLockNesting++; getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6580,8 +7149,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFullWifiLockReleasedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { mWifiFullLockNesting--; if (mWifiFullLockNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_FULL_LOCK_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) .noteFullWifiLockReleasedLocked(elapsedRealtimeMs); @@ -6597,8 +7168,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void noteWifiScanStartedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { if (mWifiScanNesting == 0) { - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_SCAN_FLAG); + mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } mWifiScanNesting++; getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) @@ -6614,8 +7187,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWifiScanStoppedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { mWifiScanNesting--; if (mWifiScanNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_SCAN_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) .noteWifiScanStoppedLocked(elapsedRealtimeMs); @@ -6640,10 +7215,14 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWifiMulticastEnabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { uid = mapUid(uid); if (mWifiMulticastNesting == 0) { - mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG); + mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + // Start Wifi Multicast overall timer if (!mWifiMulticastWakelockTimer.isRunningLocked()) { + if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started"); mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtimeMs); } } @@ -6657,12 +7236,14 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); mWifiMulticastNesting--; if (mWifiMulticastNesting == 0) { - mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG); + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); // Stop Wifi Multicast overall timer if (mWifiMulticastWakelockTimer.isRunningLocked()) { - if (DEBUG) Slog.v(TAG, "Multicast Overall Timer Stopped"); + if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped"); mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtimeMs); } } @@ -7414,9 +7995,8 @@ public class BatteryStatsImpl extends BatteryStats { // If the start clock time has changed by more than a year, then presumably // the previous time was completely bogus. So we are going to figure out a // new time based on how much time has elapsed since we started counting. - mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(), - currentTimeMs - ); + recordCurrentTimeChangeLocked(currentTimeMs, mClock.elapsedRealtime(), + mClock.uptimeMillis()); return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000)); } return mStartClockTimeMs; @@ -10648,19 +11228,18 @@ public class BatteryStatsImpl extends BatteryStats { UserInfoProvider userInfoProvider) { init(clock); - mHandler = new MyHandler(handler.getLooper()); - mConstants = new Constants(mHandler); - if (systemDir == null) { mStatsFile = null; - mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock); + mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer); } else { mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin")); - mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); + mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, systemDir, + this::getMaxHistoryFiles); } mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin")); mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml")); + mHandler = new MyHandler(handler.getLooper()); + mConstants = new Constants(mHandler); mStartCount++; initTimersAndCounters(); mOnBattery = mOnBatteryInternal = false; @@ -10669,6 +11248,7 @@ public class BatteryStatsImpl extends BatteryStats { initTimes(uptimeUs, realtimeUs); mStartPlatformVersion = mEndPlatformVersion = Build.ID; initDischarge(realtimeUs); + clearHistoryLocked(); updateDailyDeadlineLocked(); mPlatformIdleStateCallback = cb; mMeasuredEnergyRetriever = energyStatsCb; @@ -10679,6 +11259,12 @@ public class BatteryStatsImpl extends BatteryStats { FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode); } + private int getMaxHistoryFiles() { + synchronized (this) { + return mConstants.MAX_HISTORY_FILES; + } + } + @VisibleForTesting protected void initTimersAndCounters() { mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase); @@ -10760,7 +11346,7 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeUnplugLevel = 0; mDischargePlugLevel = -1; mDischargeCurrentLevel = 0; - mBatteryLevel = 0; + mCurrentBatteryLevel = 0; } public void setPowerProfileLocked(PowerProfile profile) { @@ -11147,7 +11733,7 @@ public class BatteryStatsImpl extends BatteryStats { } public int getHistoryUsedSize() { - return mHistory.getHistoryUsedSize(); + return mBatteryStatsHistory.getHistoryUsedSize(); } @Override @@ -11161,27 +11747,43 @@ public class BatteryStatsImpl extends BatteryStats { */ @VisibleForTesting public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() { - return mHistory.iterate(); + return new BatteryStatsHistoryIterator(mBatteryStatsHistory); } @Override public int getHistoryStringPoolSize() { - return mHistory.getHistoryStringPoolSize(); + return mHistoryTagPool.size(); } @Override public int getHistoryStringPoolBytes() { - return mHistory.getHistoryStringPoolBytes(); + return mNumHistoryTagChars; } @Override public String getHistoryTagPoolString(int index) { - return mHistory.getHistoryTagPoolString(index); + ensureHistoryTagArray(); + HistoryTag historyTag = mHistoryTags.get(index); + return historyTag != null ? historyTag.string : null; } @Override public int getHistoryTagPoolUid(int index) { - return mHistory.getHistoryTagPoolUid(index); + ensureHistoryTagArray(); + HistoryTag historyTag = mHistoryTags.get(index); + return historyTag != null ? historyTag.uid : Process.INVALID_UID; + } + + private void ensureHistoryTagArray() { + if (mHistoryTags != null) { + return; + } + + mHistoryTags = new SparseArray<>(mHistoryTagPool.size()); + for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) { + mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG, + entry.getKey()); + } } @Override @@ -11191,11 +11793,15 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void finishIteratingHistoryLocked() { - mBatteryStatsHistoryIterator.close(); mBatteryStatsHistoryIterator = null; } @Override + public long getHistoryBaseTime() { + return mHistoryBaseTimeMs; + } + + @Override public int getStartCount() { return mStartCount; } @@ -11248,23 +11854,24 @@ public class BatteryStatsImpl extends BatteryStats { long realtimeUs = mSecRealtime * 1000; resetAllStatsLocked(mSecUptime, mSecRealtime, RESET_REASON_ADB_COMMAND); pullPendingStateUpdatesLocked(); - mHistory.writeHistoryItem(mSecRealtime, mSecUptime); - mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel = mBatteryLevel; + addHistoryRecordLocked(mSecRealtime, mSecUptime); + mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel + = mCurrentBatteryLevel = mHistoryCur.batteryLevel; mOnBatteryTimeBase.reset(uptimeUs, realtimeUs); mOnBatteryScreenOffTimeBase.reset(uptimeUs, realtimeUs); - if (!mBatteryPluggedIn) { + if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) { if (Display.isOnState(mScreenState)) { - mDischargeScreenOnUnplugLevel = mBatteryLevel; + mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel; mDischargeScreenDozeUnplugLevel = 0; mDischargeScreenOffUnplugLevel = 0; } else if (Display.isDozeState(mScreenState)) { mDischargeScreenOnUnplugLevel = 0; - mDischargeScreenDozeUnplugLevel = mBatteryLevel; + mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel; mDischargeScreenOffUnplugLevel = 0; } else { mDischargeScreenOnUnplugLevel = 0; mDischargeScreenDozeUnplugLevel = 0; - mDischargeScreenOffUnplugLevel = mBatteryLevel; + mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel; } mDischargeAmountScreenOn = 0; mDischargeAmountScreenOff = 0; @@ -11408,12 +12015,27 @@ public class BatteryStatsImpl extends BatteryStats { resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs); + mLastHistoryStepDetails = null; + mLastStepCpuUserTimeMs = mLastStepCpuSystemTimeMs = 0; + mCurStepCpuUserTimeMs = mCurStepCpuSystemTimeMs = 0; + mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0; + mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0; + mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0; + mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0; + mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0; + mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0; + mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0; + mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0; + mNumAllUidCpuTimeReads = 0; mNumUidsRemoved = 0; initDischarge(elapsedRealtimeUs); - mHistory.reset(); + clearHistoryLocked(); + if (mBatteryStatsHistory != null) { + mBatteryStatsHistory.resetAllFiles(); + } // Flush external data, gathering snapshots, but don't process it since it is pre-reset data mIgnoreNextExternalStats = true; @@ -11436,7 +12058,7 @@ public class BatteryStatsImpl extends BatteryStats { for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) { SparseIntArray uids = ent.getValue(); for (int j=0; j<uids.size(); j++) { - mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, i, ent.getKey(), + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(), uids.keyAt(j)); } } @@ -11861,8 +12483,9 @@ public class BatteryStatsImpl extends BatteryStats { (long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt); mWifiActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked( monitoredRailChargeConsumedMaMs); - mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs, - (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR)); + mHistoryCur.wifiRailChargeMah += + (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mTmpRailStats.resetWifiTotalEnergyUsed(); if (uidEstimatedConsumptionMah != null) { @@ -11975,8 +12598,9 @@ public class BatteryStatsImpl extends BatteryStats { (long) (mTmpRailStats.getCellularTotalEnergyUseduWs() / opVolt); mModemActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked( monitoredRailChargeConsumedMaMs); - mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs, - (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR)); + mHistoryCur.modemRailChargeMah += + (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mTmpRailStats.resetCellularTotalEnergyUsed(); } @@ -12244,8 +12868,8 @@ public class BatteryStatsImpl extends BatteryStats { } } if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) { - mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG); + mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG; + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } @@ -13678,7 +14302,11 @@ public class BatteryStatsImpl extends BatteryStats { mHandler.removeCallbacks(mDeferSetCharging); if (mCharging != charging) { mCharging = charging; - mHistory.setChargingState(charging); + if (charging) { + mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG; + } else { + mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG; + } mHandler.sendEmptyMessage(MSG_REPORT_CHARGING); return true; } @@ -13692,15 +14320,6 @@ public class BatteryStatsImpl extends BatteryStats { mSystemReady = true; } - /** - * Force recording of all history events regardless of the "charging" state. - */ - @VisibleForTesting - public void forceRecordAllHistory() { - mHistory.forceRecordAllHistory(); - mRecordAllHistory = true; - } - @GuardedBy("this") protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery, final int oldStatus, final int level, final int chargeUah) { @@ -13784,12 +14403,15 @@ public class BatteryStatsImpl extends BatteryStats { mInitStepMode = mCurStepMode; mModStepMode = 0; pullPendingStateUpdatesLocked(); + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: " + + Integer.toHexString(mHistoryCur.states)); if (reset) { - mHistory.startRecordingHistory(mSecRealtime, mSecUptime, reset); - initActiveHistoryEventsLocked(mSecRealtime, mSecUptime); + mRecordingHistory = true; + startRecordingHistory(mSecRealtime, mSecUptime, reset); } - mBatteryPluggedIn = false; - mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn); + addHistoryRecordLocked(mSecRealtime, mSecUptime); mDischargeCurrentLevel = mDischargeUnplugLevel = level; if (Display.isOnState(screenState)) { mDischargeScreenOnUnplugLevel = level; @@ -13811,8 +14433,11 @@ public class BatteryStatsImpl extends BatteryStats { } else { mOnBattery = mOnBatteryInternal = false; pullPendingStateUpdatesLocked(); - mBatteryPluggedIn = true; - mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn); + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(mSecRealtime, mSecUptime); mDischargeCurrentLevel = mDischargePlugLevel = level; if (level < mDischargeUnplugLevel) { mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1; @@ -13827,12 +14452,45 @@ public class BatteryStatsImpl extends BatteryStats { mModStepMode = 0; } if (doWrite || (mLastWriteTimeMs + (60 * 1000)) < mSecRealtime) { - if (mStatsFile != null && !mHistory.isReadOnly()) { + if (mStatsFile != null && mBatteryStatsHistory.getActiveFile() != null) { writeAsyncLocked(); } } } + @GuardedBy("this") + private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs, + boolean reset) { + mRecordingHistory = true; + mHistoryCur.currentTime = mClock.currentTimeMillis(); + addHistoryBufferLocked(elapsedRealtimeMs, + reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME, + mHistoryCur); + mHistoryCur.currentTime = 0; + if (reset) { + initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs); + } + } + + @GuardedBy("this") + private void recordCurrentTimeChangeLocked(final long currentTimeMs, + final long elapsedRealtimeMs, final long uptimeMs) { + if (mRecordingHistory) { + mHistoryCur.currentTime = currentTimeMs; + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur); + mHistoryCur.currentTime = 0; + } + } + + @GuardedBy("this") + private void recordShutdownLocked(final long currentTimeMs, final long elapsedRealtimeMs) { + if (mRecordingHistory) { + mHistoryCur.currentTime = currentTimeMs; + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur); + mHistoryCur.currentTime = 0; + } + } + private void scheduleSyncExternalStatsLocked(String reason, int updateFlags) { if (mExternalSync != null) { mExternalSync.scheduleSync(reason, updateFlags); @@ -13850,7 +14508,8 @@ public class BatteryStatsImpl extends BatteryStats { // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0. temp = Math.max(0, temp); - reportChangesToStatsLog(status, plugType, level); + reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null, + status, plugType, level); final boolean onBattery = isOnBattery(plugType, status); if (!mHaveBatteryLevel) { @@ -13860,47 +14519,52 @@ public class BatteryStatsImpl extends BatteryStats { // plugged in, then twiddle our state to correctly reflect that // since we won't be going through the full setOnBattery(). if (onBattery == mOnBattery) { - mHistory.setPluggedInState(!onBattery); + if (onBattery) { + mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + } else { + mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + } } - mBatteryStatus = status; - mBatteryLevel = level; - mBatteryChargeUah = chargeUah; - // Always start out assuming charging, that will be updated later. - mHistory.setBatteryState(true /* charging */, status, level, chargeUah); - + mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG; + mHistoryCur.batteryStatus = (byte)status; + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.batteryChargeUah = chargeUah; mMaxChargeStepLevel = mMinDischargeStepLevel = mLastChargeStepLevel = mLastDischargeStepLevel = level; - } else if (mBatteryLevel != level || mOnBattery != onBattery) { + } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) { recordDailyStatsIfNeededLocked(level >= 100 && onBattery, currentTimeMs); } - int oldStatus = mBatteryStatus; + int oldStatus = mHistoryCur.batteryStatus; if (onBattery) { mDischargeCurrentLevel = level; - if (!mHistory.isRecordingHistory()) { - mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true); + if (!mRecordingHistory) { + mRecordingHistory = true; + startRecordingHistory(elapsedRealtimeMs, uptimeMs, true); } } else if (level < 96 && status != BatteryManager.BATTERY_STATUS_UNKNOWN) { - if (!mHistory.isRecordingHistory()) { - mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true); + if (!mRecordingHistory) { + mRecordingHistory = true; + startRecordingHistory(elapsedRealtimeMs, uptimeMs, true); } } + mBatteryVoltageMv = voltageMv; + mCurrentBatteryLevel = level; if (mDischargePlugLevel < 0) { mDischargePlugLevel = level; } if (onBattery != mOnBattery) { - mBatteryLevel = level; - mBatteryStatus = status; - mBatteryHealth = health; - mBatteryPlugType = plugType; - mBatteryTemperature = temp; - mBatteryVoltageMv = voltageMv; - mHistory.setBatteryState(status, level, health, plugType, temp, voltageMv, chargeUah); - if (chargeUah < mBatteryChargeUah) { + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.batteryStatus = (byte)status; + mHistoryCur.batteryHealth = (byte)health; + mHistoryCur.batteryPlugType = (byte)plugType; + mHistoryCur.batteryTemperature = (short)temp; + mHistoryCur.batteryVoltage = (char) voltageMv; + if (chargeUah < mHistoryCur.batteryChargeUah) { // Only record discharges - final long chargeDiff = (long) mBatteryChargeUah - chargeUah; + final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah; mDischargeCounter.addCountLocked(chargeDiff); mDischargeScreenOffCounter.addCountLocked(chargeDiff); if (Display.isDozeState(mScreenState)) { @@ -13912,12 +14576,12 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeDeepDozeCounter.addCountLocked(chargeDiff); } } - mBatteryChargeUah = chargeUah; + mHistoryCur.batteryChargeUah = chargeUah; setOnBatteryLocked(elapsedRealtimeMs, uptimeMs, onBattery, oldStatus, level, chargeUah); } else { boolean changed = false; - if (mBatteryLevel != level) { - mBatteryLevel = level; + if (mHistoryCur.batteryLevel != level) { + mHistoryCur.batteryLevel = (byte)level; changed = true; // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record @@ -13925,33 +14589,33 @@ public class BatteryStatsImpl extends BatteryStats { mExternalSync.scheduleSyncDueToBatteryLevelChange( mConstants.BATTERY_LEVEL_COLLECTION_DELAY_MS); } - if (mBatteryStatus != status) { - mBatteryStatus = status; + if (mHistoryCur.batteryStatus != status) { + mHistoryCur.batteryStatus = (byte)status; changed = true; } - if (mBatteryHealth != health) { - mBatteryHealth = health; + if (mHistoryCur.batteryHealth != health) { + mHistoryCur.batteryHealth = (byte)health; changed = true; } - if (mBatteryPlugType != plugType) { - mBatteryPlugType = plugType; + if (mHistoryCur.batteryPlugType != plugType) { + mHistoryCur.batteryPlugType = (byte)plugType; changed = true; } - if (temp >= (mBatteryTemperature + 10) - || temp <= (mBatteryTemperature - 10)) { - mBatteryTemperature = temp; + if (temp >= (mHistoryCur.batteryTemperature+10) + || temp <= (mHistoryCur.batteryTemperature-10)) { + mHistoryCur.batteryTemperature = (short)temp; changed = true; } - if (voltageMv > (mBatteryVoltageMv + 20) - || voltageMv < (mBatteryVoltageMv - 20)) { - mBatteryVoltageMv = voltageMv; + if (voltageMv > (mHistoryCur.batteryVoltage + 20) + || voltageMv < (mHistoryCur.batteryVoltage - 20)) { + mHistoryCur.batteryVoltage = (char) voltageMv; changed = true; } - if (chargeUah >= (mBatteryChargeUah + 10) - || chargeUah <= (mBatteryChargeUah - 10)) { - if (chargeUah < mBatteryChargeUah) { + if (chargeUah >= (mHistoryCur.batteryChargeUah + 10) + || chargeUah <= (mHistoryCur.batteryChargeUah - 10)) { + if (chargeUah < mHistoryCur.batteryChargeUah) { // Only record discharges - final long chargeDiff = (long) mBatteryChargeUah - chargeUah; + final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah; mDischargeCounter.addCountLocked(chargeDiff); mDischargeScreenOffCounter.addCountLocked(chargeDiff); if (Display.isDozeState(mScreenState)) { @@ -13963,10 +14627,9 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeDeepDozeCounter.addCountLocked(chargeDiff); } } - mBatteryChargeUah = chargeUah; + mHistoryCur.batteryChargeUah = chargeUah; changed = true; } - long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT) | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT) | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT); @@ -14024,10 +14687,7 @@ public class BatteryStatsImpl extends BatteryStats { mLastChargeStepLevel = level; } if (changed) { - mHistory.setBatteryState(mBatteryStatus, mBatteryLevel, mBatteryHealth, - mBatteryPlugType, mBatteryTemperature, mBatteryVoltageMv, - mBatteryChargeUah); - mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs); + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } } if (!onBattery && @@ -14036,7 +14696,7 @@ public class BatteryStatsImpl extends BatteryStats { // We don't record history while we are plugged in and fully charged // (or when battery is not present). The next time we are // unplugged, history will be cleared. - mHistory.setHistoryRecordingEnabled(DEBUG); + mRecordingHistory = DEBUG; } mLastLearnedBatteryCapacityUah = chargeFullUah; @@ -14055,18 +14715,17 @@ public class BatteryStatsImpl extends BatteryStats { } // Inform StatsLog of setBatteryState changes. - private void reportChangesToStatsLog(final int status, final int plugType, final int level) { - if (!mHaveBatteryLevel) { - return; - } + // If this is the first reporting, pass in recentPast == null. + private void reportChangesToStatsLog(HistoryItem recentPast, + final int status, final int plugType, final int level) { - if (mBatteryStatus != status) { + if (recentPast == null || recentPast.batteryStatus != status) { FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status); } - if (mBatteryPlugType != plugType) { + if (recentPast == null || recentPast.batteryPlugType != plugType) { FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType); } - if (mBatteryLevel != level) { + if (recentPast == null || recentPast.batteryLevel != level) { FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level); } } @@ -14136,7 +14795,7 @@ public class BatteryStatsImpl extends BatteryStats { if (msPerLevel <= 0) { return -1; } - return (msPerLevel * mBatteryLevel) * 1000; + return (msPerLevel * mCurrentBatteryLevel) * 1000; } @Override @@ -14166,7 +14825,7 @@ public class BatteryStatsImpl extends BatteryStats { if (msPerLevel <= 0) { return -1; } - return (msPerLevel * (100 - mBatteryLevel)) * 1000; + return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000; } /*@hide */ @@ -14597,8 +15256,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void shutdownLocked() { - mHistory.recordShutdownEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), - mClock.currentTimeMillis()); + recordShutdownLocked(mClock.currentTimeMillis(), mClock.elapsedRealtime()); writeSyncLocked(); mShuttingDown = true; } @@ -14806,6 +15464,7 @@ public class BatteryStatsImpl extends BatteryStats { PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong( KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS, DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS); + MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES, ActivityManager.isLowRamDeviceStatic() ? DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE @@ -14816,20 +15475,9 @@ public class BatteryStatsImpl extends BatteryStats { : DEFAULT_MAX_HISTORY_BUFFER_KB) * 1024; updateBatteryChargedDelayMsLocked(); - - onChange(); } } - /** - * Propagates changes in constant values. - */ - @VisibleForTesting - public void onChange() { - mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES); - mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER); - } - private void updateBatteryChargedDelayMsLocked() { // a negative value indicates that we should ignore this override final int delay = Settings.Global.getInt(mResolver, @@ -15050,11 +15698,27 @@ public class BatteryStatsImpl extends BatteryStats { } private void writeHistoryLocked() { + if (mBatteryStatsHistory.getActiveFile() == null) { + Slog.w(TAG, "writeHistoryLocked: no history file associated with this instance"); + return; + } + if (mShuttingDown) { return; } - mHistory.writeHistory(); + Parcel p = Parcel.obtain(); + try { + final long start = SystemClock.uptimeMillis(); + writeHistoryBuffer(p, true); + if (DEBUG) { + Slog.d(TAG, "writeHistoryBuffer duration ms:" + + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize()); + } + writeParcelToFileLocked(p, mBatteryStatsHistory.getActiveFile()); + } finally { + p.recycle(); + } } private final ReentrantLock mWriteLock = new ReentrantLock(); @@ -15093,6 +15757,13 @@ public class BatteryStatsImpl extends BatteryStats { return; } + final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile(); + if (activeHistoryFile == null) { + Slog.w(TAG, + "readLocked: no history file associated with this instance"); + return; + } + mUidStats.clear(); Parcel stats = Parcel.obtain(); @@ -15105,7 +15776,7 @@ public class BatteryStatsImpl extends BatteryStats { readSummaryFromParcel(stats); if (DEBUG) { Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath() - + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis() + + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis() - start)); } } @@ -15117,19 +15788,126 @@ public class BatteryStatsImpl extends BatteryStats { stats.recycle(); } - if (!mHistory.readSummary()) { - resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(), - RESET_REASON_CORRUPT_FILE); + Parcel history = Parcel.obtain(); + try { + final long start = SystemClock.uptimeMillis(); + if (activeHistoryFile.exists()) { + byte[] raw = activeHistoryFile.readFully(); + if (raw.length > 0) { + history.unmarshall(raw, 0, raw.length); + history.setDataPosition(0); + readHistoryBuffer(history); + } + if (DEBUG) { + Slog.d(TAG, "readLocked history file::" + + activeHistoryFile.getBaseFile().getPath() + + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis() + - start)); + } + } + } catch (Exception e) { + Slog.e(TAG, "Error reading battery history", e); + clearHistoryLocked(); + mBatteryStatsHistory.resetAllFiles(); + } finally { + history.recycle(); } mEndPlatformVersion = Build.ID; - mHistory.continueRecordingHistory(); + if (mHistoryBuffer.dataPosition() > 0 + || mBatteryStatsHistory.getFilesNumbers().size() > 1) { + mRecordingHistory = true; + final long elapsedRealtimeMs = mClock.elapsedRealtime(); + final long uptimeMs = mClock.uptimeMillis(); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_START, mHistoryCur); + startRecordingHistory(elapsedRealtimeMs, uptimeMs, false); + } recordDailyStatsIfNeededLocked(false, mClock.currentTimeMillis()); } @GuardedBy("this") + void readHistoryBuffer(Parcel in) throws ParcelFormatException { + final int version = in.readInt(); + if (version != BatteryStatsHistory.VERSION) { + Slog.w("BatteryStats", "readHistoryBuffer: version got " + version + + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats"); + return; + } + + final long historyBaseTime = in.readLong(); + + mHistoryBuffer.setDataSize(0); + mHistoryBuffer.setDataPosition(0); + + int bufSize = in.readInt(); + int curPos = in.dataPosition(); + if (bufSize >= (mConstants.MAX_HISTORY_BUFFER*100)) { + throw new ParcelFormatException("File corrupt: history data buffer too large " + + bufSize); + } else if ((bufSize&~3) != bufSize) { + throw new ParcelFormatException("File corrupt: history data buffer not aligned " + + bufSize); + } else { + if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize + + " bytes at " + curPos); + mHistoryBuffer.appendFrom(in, curPos, bufSize); + in.setDataPosition(curPos + bufSize); + } + + if (DEBUG_HISTORY) { + StringBuilder sb = new StringBuilder(128); + sb.append("****************** OLD mHistoryBaseTimeMs: "); + TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); + Slog.i(TAG, sb.toString()); + } + mHistoryBaseTimeMs = historyBaseTime; + if (DEBUG_HISTORY) { + StringBuilder sb = new StringBuilder(128); + sb.append("****************** NEW mHistoryBaseTimeMs: "); + TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); + Slog.i(TAG, sb.toString()); + } + + // We are just arbitrarily going to insert 1 minute from the sample of + // the last run until samples in this run. + if (mHistoryBaseTimeMs > 0) { + long oldnow = mClock.elapsedRealtime(); + mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1; + if (DEBUG_HISTORY) { + StringBuilder sb = new StringBuilder(128); + sb.append("****************** ADJUSTED mHistoryBaseTimeMs: "); + TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); + Slog.i(TAG, sb.toString()); + } + } + } + + void writeHistoryBuffer(Parcel out, boolean inclData) { + if (DEBUG_HISTORY) { + StringBuilder sb = new StringBuilder(128); + sb.append("****************** WRITING mHistoryBaseTimeMs: "); + TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); + sb.append(" mLastHistoryElapsedRealtimeMs: "); + TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb); + Slog.i(TAG, sb.toString()); + } + out.writeInt(BatteryStatsHistory.VERSION); + out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs); + if (!inclData) { + out.writeInt(0); + out.writeInt(0); + return; + } + + out.writeInt(mHistoryBuffer.dataSize()); + if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: " + + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition()); + out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize()); + } + + @GuardedBy("this") public void readSummaryFromParcel(Parcel in) throws ParcelFormatException { final int version = in.readInt(); @@ -15139,7 +15917,31 @@ public class BatteryStatsImpl extends BatteryStats { return; } - mHistory.readSummaryFromParcel(in); + boolean inclHistory = in.readBoolean(); + if (inclHistory) { + readHistoryBuffer(in); + mBatteryStatsHistory.readFromParcel(in); + } + + mHistoryTagPool.clear(); + mNextHistoryTagIdx = 0; + mNumHistoryTagChars = 0; + + int numTags = in.readInt(); + for (int i=0; i<numTags; i++) { + int idx = in.readInt(); + String str = in.readString(); + int uid = in.readInt(); + HistoryTag tag = new HistoryTag(); + tag.string = str; + tag.uid = uid; + tag.poolIdx = idx; + mHistoryTagPool.put(tag, idx); + if (idx >= mNextHistoryTagIdx) { + mNextHistoryTagIdx = idx+1; + } + mNumHistoryTagChars += tag.string.length() + 1; + } mStartCount = in.readInt(); mUptimeUs = in.readLong(); @@ -15152,7 +15954,7 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeUnplugLevel = in.readInt(); mDischargePlugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); - mBatteryLevel = in.readInt(); + mCurrentBatteryLevel = in.readInt(); mEstimatedBatteryCapacityMah = in.readInt(); mLastLearnedBatteryCapacityUah = in.readInt(); mMinLearnedBatteryCapacityUah = in.readInt(); @@ -15655,7 +16457,19 @@ public class BatteryStatsImpl extends BatteryStats { out.writeInt(VERSION); - mHistory.writeSummaryToParcel(out, inclHistory); + out.writeBoolean(inclHistory); + if (inclHistory) { + writeHistoryBuffer(out, true); + mBatteryStatsHistory.writeToParcel(out); + } + + out.writeInt(mHistoryTagPool.size()); + for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) { + HistoryTag tag = ent.getKey(); + out.writeInt(ent.getValue()); + out.writeString(tag.string); + out.writeInt(tag.uid); + } out.writeInt(mStartCount); out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED)); @@ -15668,7 +16482,7 @@ public class BatteryStatsImpl extends BatteryStats { out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargePlugLevel); out.writeInt(mDischargeCurrentLevel); - out.writeInt(mBatteryLevel); + out.writeInt(mCurrentBatteryLevel); out.writeInt(mEstimatedBatteryCapacityMah); out.writeInt(mLastLearnedBatteryCapacityUah); out.writeInt(mMinLearnedBatteryCapacityUah); diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index c36d950b6cf6..0cdd4d101459 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -22,6 +22,7 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Parcel; import android.os.Process; import android.os.SystemClock; import android.os.UidBatteryConsumer; @@ -31,8 +32,10 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.PowerProfile; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -217,7 +220,18 @@ public class BatteryUsageStatsProvider { } BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats; - batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory()); + + // Make a copy of battery history to avoid concurrent modification. + Parcel historyBuffer = Parcel.obtain(); + historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0, + batteryStatsImpl.mHistoryBuffer.dataSize()); + + final File systemDir = + batteryStatsImpl.mBatteryStatsHistory.getHistoryDirectory().getParentFile(); + final BatteryStatsHistory batteryStatsHistory = + new BatteryStatsHistory(historyBuffer, systemDir, null); + + batteryUsageStatsBuilder.setBatteryHistory(batteryStatsHistory); } BatteryUsageStats stats = batteryUsageStatsBuilder.build(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 7e9474395f0c..765e7f08d201 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3245,7 +3245,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A rootTask.moveToFront(reason, task); // Report top activity change to tracking services and WM if (mRootWindowContainer.getTopResumedActivity() == this) { - mAtmService.setResumedActivityUncheckLocked(this, reason); + mAtmService.setLastResumedActivityUncheckLocked(this, reason); } return true; } @@ -5887,7 +5887,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, PauseActivityItem.obtain(finishing, false /* userLeaving */, - configChangeFlags, false /* dontReport */)); + configChangeFlags, false /* dontReport */, + false /* autoEnteringPip */)); } catch (Exception e) { Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index de3b2a686dde..4003eeb61abe 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3563,7 +3563,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // Continue the pausing process after entering pip. if (r.isState(PAUSING)) { r.getTask().schedulePauseActivity(r, false /* userLeaving */, - false /* pauseImmediately */, "auto-pip"); + false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip"); } } }; @@ -4624,7 +4624,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** Update AMS states when an activity is resumed. */ - void setResumedActivityUncheckLocked(ActivityRecord r, String reason) { + void setLastResumedActivityUncheckLocked(ActivityRecord r, String reason) { final Task task = r.getTask(); if (task.isActivityTypeStandard()) { if (mCurAppTimeTracker != r.appTimeTracker) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 5a1afc49c62b..dc91c1597128 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -789,7 +789,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // schedule launch ticks to collect information about slow apps. r.startLaunchTickingLocked(); - + r.lastLaunchTime = SystemClock.uptimeMillis(); r.setProcess(proc); // Ensure activity is allowed to be resumed after process has set. @@ -835,8 +835,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final IActivityClientController activityClientController = proc.hasEverLaunchedActivity() ? null : mService.mActivityClientController; r.launchCount++; - r.lastLaunchTime = SystemClock.uptimeMillis(); - proc.setLastActivityLaunchTime(r.lastLaunchTime); if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r); @@ -2085,7 +2083,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { * activity releases the top state and reports back, message about acquiring top state will be * sent to the new top resumed activity. */ - void updateTopResumedActivityIfNeeded() { + void updateTopResumedActivityIfNeeded(String reason) { final ActivityRecord prevTopActivity = mTopResumedActivity; final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) { @@ -2121,6 +2119,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } mService.updateOomAdj(); } + // Update the last resumed activity and focused app when the top resumed activity changed + // because the new top resumed activity might be already resumed and thus won't have + // activity state change to update the records to AMS. + if (mTopResumedActivity != null) { + mService.setLastResumedActivityUncheckLocked(mTopResumedActivity, reason); + } scheduleTopResumedActivityStateIfNeeded(); mService.updateTopApp(mTopResumedActivity); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 95169dbd7092..55d6b2fe8226 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -375,6 +375,9 @@ public class AppTransition implements Dump { final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null; int redoLayout = notifyAppTransitionStartingLocked( + AppTransition.isKeyguardGoingAwayTransitOld(transit), + AppTransition.isKeyguardOccludeTransitOld(transit), + topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, topOpeningAnim != null ? topOpeningAnim.getStatusBarTransitionsStartTime() : SystemClock.uptimeMillis(), @@ -413,11 +416,8 @@ public class AppTransition implements Dump { } void freeze() { - final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains( + final boolean keyguardGoingAway = mNextAppTransitionRequests.contains( TRANSIT_KEYGUARD_GOING_AWAY); - final boolean keyguardOccludedCancelled = - mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_OCCLUDE) - || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_UNOCCLUDE); // The RemoteAnimationControl didn't register AppTransitionListener and // only initialized the finish and timeout callback when goodToGo(). @@ -429,7 +429,7 @@ public class AppTransition implements Dump { mNextAppTransitionRequests.clear(); clear(); setReady(); - notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled, keyguardOccludedCancelled); + notifyAppTransitionCancelledLocked(keyguardGoingAway); } private void setAppTransitionState(int state) { @@ -479,11 +479,9 @@ public class AppTransition implements Dump { } } - private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) { + private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAway) { for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled, - keyguardOccludedCancelled); + mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAway); } } @@ -493,12 +491,14 @@ public class AppTransition implements Dump { } } - private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime, + private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOcclude, long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { int redoLayout = 0; for (int i = 0; i < mListeners.size(); i++) { - redoLayout |= mListeners.get(i).onAppTransitionStartingLocked( - statusBarAnimationStartTime, statusBarAnimationDuration); + redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, + keyguardOcclude, duration, statusBarAnimationStartTime, + statusBarAnimationDuration); } return redoLayout; } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 4b0005d77e40..44f388b6ed39 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -20,6 +20,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; @@ -91,6 +95,7 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionFlags; import android.view.WindowManager.TransitionOldType; import android.view.WindowManager.TransitionType; +import android.view.animation.Animation; import android.window.ITaskFragmentOrganizer; import com.android.internal.annotations.VisibleForTesting; @@ -292,6 +297,7 @@ public class AppTransitionController { final int flags = appTransition.getTransitFlags(); layoutRedo = appTransition.goodToGo(transit, topOpeningApp); + handleNonAppWindowsInTransition(transit, flags); appTransition.postAnimationCallback(); appTransition.clear(); } finally { @@ -1165,6 +1171,30 @@ public class AppTransitionController { } } + private void handleNonAppWindowsInTransition(@TransitionOldType int transit, int flags) { + if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY + && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { + Animation anim = mService.mPolicy.createKeyguardWallpaperExit( + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); + if (anim != null) { + anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked()); + mDisplayContent.mWallpaperController.startWallpaperAnimation(anim); + } + } + } + if ((transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY + || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER) + && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { + mDisplayContent.startKeyguardExitOnNonAppWindows( + transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); + } + } + private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps, ArrayMap<WindowContainer, Integer> outReasons) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index 994f07959f3b..5a2409927ecc 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -17,8 +17,12 @@ package com.android.server.wm; import android.annotation.UiThread; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; import android.os.Build; import android.os.Handler; @@ -305,21 +309,21 @@ class AppWarnings { private void hideDialogsForPackageUiThread(String name) { // Hides the "unsupported display" dialog if necessary. if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals( - mUnsupportedDisplaySizeDialog.getPackageName()))) { + mUnsupportedDisplaySizeDialog.mPackageName))) { mUnsupportedDisplaySizeDialog.dismiss(); mUnsupportedDisplaySizeDialog = null; } // Hides the "unsupported compile SDK" dialog if necessary. if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals( - mUnsupportedCompileSdkDialog.getPackageName()))) { + mUnsupportedCompileSdkDialog.mPackageName))) { mUnsupportedCompileSdkDialog.dismiss(); mUnsupportedCompileSdkDialog = null; } // Hides the "deprecated target sdk version" dialog if necessary. if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals( - mDeprecatedTargetSdkVersionDialog.getPackageName()))) { + mDeprecatedTargetSdkVersionDialog.mPackageName))) { mDeprecatedTargetSdkVersionDialog.dismiss(); mDeprecatedTargetSdkVersionDialog = null; } @@ -431,6 +435,49 @@ class AppWarnings { } } + static class BaseDialog { + final AppWarnings mManager; + final String mPackageName; + AlertDialog mDialog; + private BroadcastReceiver mCloseReceiver; + + BaseDialog(AppWarnings manager, String packageName) { + mManager = manager; + mPackageName = packageName; + } + + @UiThread + void show() { + if (mDialog == null) return; + if (mCloseReceiver == null) { + mCloseReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + mManager.mUiHandler.hideDialogsForPackage(mPackageName); + } + } + }; + mManager.mUiContext.registerReceiver(mCloseReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), + Context.RECEIVER_EXPORTED); + } + Slog.w(TAG, "Showing " + getClass().getSimpleName() + " for package " + mPackageName); + mDialog.show(); + } + + @UiThread + void dismiss() { + if (mDialog == null) return; + if (mCloseReceiver != null) { + mManager.mUiContext.unregisterReceiver(mCloseReceiver); + mCloseReceiver = null; + } + mDialog.dismiss(); + mDialog = null; + } + } + /** * Handles messages on the ActivityTaskManagerService thread. */ diff --git a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java index 37244bd5bd06..1a7a9b258400 100644 --- a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java +++ b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java @@ -16,31 +16,23 @@ package com.android.server.wm; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; - import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; -import android.util.Log; import android.view.Window; import android.view.WindowManager; import com.android.internal.R; import com.android.server.utils.AppInstallerUtil; -public class DeprecatedTargetSdkVersionDialog { - private static final String TAG = TAG_WITH_CLASS_NAME ? "DeprecatedTargetSdkVersionDialog" : TAG_ATM; - - private final AlertDialog mDialog; - private final String mPackageName; +class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog { - public DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context, + DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context, ApplicationInfo appInfo) { - mPackageName = appInfo.packageName; + super(manager, appInfo.packageName); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -75,17 +67,4 @@ public class DeprecatedTargetSdkVersionDialog { // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. window.getAttributes().setTitle("DeprecatedTargetSdkVersionDialog"); } - - public String getPackageName() { - return mPackageName; - } - - public void show() { - Log.w(TAG, "Showing SDK deprecation warning for package " + mPackageName); - mDialog.show(); - } - - public void dismiss() { - mDialog.dismiss(); - } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c6fa4c1874be..8a34af3b0107 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6598,8 +6598,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override - public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) { + public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { // It is only needed when freezing display in legacy transition. if (mTransitionController.isShellTransitionsEnabled()) return; continueUpdateOrientationForDiffOrienLaunchingApp(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 2d05f07f31c4..1a34c93f2ad6 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -602,8 +602,9 @@ public class DisplayPolicy { } @Override - public int onAppTransitionStartingLocked(long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); if (statusBar != null) { @@ -615,8 +616,7 @@ public class DisplayPolicy { } @Override - public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) { + public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { mHandler.post(mAppTransitionCancelled); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index db79eae1d80c..5b702eac7059 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -161,15 +161,15 @@ public class RecentsAnimationController implements DeathRecipient { */ final AppTransitionListener mAppTransitionListener = new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(long statusBarAnimationStartTime, + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { continueDeferredCancel(); return 0; } @Override - public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) { + public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { continueDeferredCancel(); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 552c6a530544..077f8b55e5e6 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -512,7 +512,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void onChildPositionChanged(WindowContainer child) { mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, !mWmService.mPerDisplayFocusEnabled /* updateInputWindows */); - mTaskSupervisor.updateTopResumedActivityIfNeeded(); + mTaskSupervisor.updateTopResumedActivityIfNeeded("onChildPositionChanged"); } @Override diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0332935348e9..384a4d17c752 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -920,7 +920,7 @@ class Task extends TaskFragment { // If the original state is resumed, there is no state change to update focused app. // So here makes sure the activity focus is set if it is the top. if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(r, reason); + mAtmService.setLastResumedActivityUncheckLocked(r, reason); } } if (!animate) { @@ -2433,11 +2433,7 @@ class Task extends TaskFragment { focusableTask.moveToFront(myReason); // Top display focused root task is changed, update top resumed activity if needed. if (rootTask.getTopResumedActivity() != null) { - mTaskSupervisor.updateTopResumedActivityIfNeeded(); - // Set focused app directly because if the next focused activity is already resumed - // (e.g. the next top activity is on a different display), there won't have activity - // state change to update it. - mAtmService.setResumedActivityUncheckLocked(rootTask.getTopResumedActivity(), reason); + mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); } return rootTask; } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 52bf220a647a..4063cae42b6b 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -323,6 +323,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Clear preferred top because the adding focusable task has a higher z-order. mPreferredTopFocusableRootTask = null; } + + // Update the top resumed activity because the preferred top focusable task may be changed. + mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("addChildTask"); + mAtmService.updateSleepIfNeededLocked(); onRootTaskOrderChanged(task); } @@ -416,12 +420,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } // Update the top resumed activity because the preferred top focusable task may be changed. - mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded(); - - final ActivityRecord r = child.getTopResumedActivity(); - if (r != null && r == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt"); - } + mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("positionChildTaskAt"); if (mChildren.indexOf(child) != oldPosition) { onRootTaskOrderChanged(child); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 44b5b886a5f6..1b7c6d25f9c6 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -460,7 +460,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final ActivityRecord prevR = mResumedActivity; mResumedActivity = r; - mTaskSupervisor.updateTopResumedActivityIfNeeded(); + mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); if (r == null && prevR.mDisplayContent != null && prevR.mDisplayContent.getFocusedRootTask() == null) { // Only need to notify DWPC when no activity will resume. @@ -773,9 +773,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason); } setResumedActivity(record, reason + " - onActivityStateChanged"); - if (record == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(record, reason); - } mTaskSupervisor.mRecentTasks.add(record.getTask()); } } @@ -1621,7 +1618,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode " + "directly: %s, didAutoPip: %b", prev, didAutoPip); } else { - schedulePauseActivity(prev, userLeaving, pauseImmediately, reason); + schedulePauseActivity(prev, userLeaving, pauseImmediately, + false /* autoEnteringPip */, reason); } } else { mPausingActivity = null; @@ -1675,7 +1673,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { } void schedulePauseActivity(ActivityRecord prev, boolean userLeaving, - boolean pauseImmediately, String reason) { + boolean pauseImmediately, boolean autoEnteringPip, String reason) { ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev); try { EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), @@ -1683,7 +1681,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately)); + prev.configChangeFlags, pauseImmediately, autoEnteringPip)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 88059e1a0d04..d615583f4d7f 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -49,7 +49,9 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; +import android.window.WindowContainerTransaction; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import java.lang.annotation.Retention; @@ -68,6 +70,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ActivityTaskManagerService mAtmService; private final WindowManagerGlobalLock mGlobalLock; + private final WindowOrganizerController mWindowOrganizerController; + /** * A Map which manages the relationship between * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState} @@ -82,9 +86,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ArraySet<Task> mTmpTaskSet = new ArraySet<>(); - TaskFragmentOrganizerController(ActivityTaskManagerService atm) { - mAtmService = atm; + TaskFragmentOrganizerController(@NonNull ActivityTaskManagerService atm, + @NonNull WindowOrganizerController windowOrganizerController) { + mAtmService = requireNonNull(atm); mGlobalLock = atm.mGlobalLock; + mWindowOrganizerController = requireNonNull(windowOrganizerController); } /** @@ -131,6 +137,14 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions = new SparseArray<>(); + /** + * List of {@link TaskFragmentTransaction#getTransactionToken()} that have been sent to the + * organizer. If the transaction is sent during a transition, the + * {@link TransitionController} will wait until the transaction is finished. + * @see #onTransactionFinished(IBinder) + */ + private final List<IBinder> mRunningTransactions = new ArrayList<>(); + TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) { mOrganizer = organizer; mOrganizerPid = pid; @@ -176,6 +190,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr taskFragment.removeImmediately(); mOrganizedTaskFragments.remove(taskFragment); } + for (int i = mRunningTransactions.size() - 1; i >= 0; i--) { + // Cleanup any running transaction to unblock the current transition. + onTransactionFinished(mRunningTransactions.get(i)); + } mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/); } @@ -320,6 +338,40 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setActivityIntent(activity.intent) .setActivityToken(activityToken); } + + void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) { + if (transaction.isEmpty()) { + return; + } + try { + mOrganizer.onTransactionReady(transaction); + } catch (RemoteException e) { + Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); + return; + } + onTransactionStarted(transaction.getTransactionToken()); + } + + /** Called when the transaction is sent to the organizer. */ + void onTransactionStarted(@NonNull IBinder transactionToken) { + if (!mWindowOrganizerController.getTransitionController().isCollecting()) { + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Defer transition ready for TaskFragmentTransaction=%s", transactionToken); + mRunningTransactions.add(transactionToken); + mWindowOrganizerController.getTransitionController().deferTransitionReady(); + } + + /** Called when the transaction is finished. */ + void onTransactionFinished(@NonNull IBinder transactionToken) { + if (!mRunningTransactions.remove(transactionToken)) { + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Continue transition ready for TaskFragmentTransaction=%s", transactionToken); + mWindowOrganizerController.getTransitionController().continueTransitionReady(); + } } @Nullable @@ -336,7 +388,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerOrganizer(ITaskFragmentOrganizer organizer) { + public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -354,7 +406,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void unregisterOrganizer(ITaskFragmentOrganizer organizer) { + public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) { validateAndGetState(organizer); final int pid = Binder.getCallingPid(); final long uid = Binder.getCallingUid(); @@ -372,8 +424,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId, - RemoteAnimationDefinition definition) { + public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId, + @NonNull RemoteAnimationDefinition definition) { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -398,7 +450,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) { + public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) { final int pid = Binder.getCallingPid(); final long uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -416,6 +468,17 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + @Override + public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer, + @NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) { + synchronized (mGlobalLock) { + // Keep the calling identity to avoid unsecure change. + mWindowOrganizerController.applyTransaction(wct); + final TaskFragmentOrganizerState state = validateAndGetState(organizer); + state.onTransactionFinished(transactionToken); + } + } + /** * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode. @@ -775,13 +838,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final int organizerNum = mPendingTaskFragmentEvents.size(); for (int i = 0; i < organizerNum; i++) { - final ITaskFragmentOrganizer organizer = mTaskFragmentOrganizerState.get( - mPendingTaskFragmentEvents.keyAt(i)).mOrganizer; - dispatchPendingEvents(organizer, mPendingTaskFragmentEvents.valueAt(i)); + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(mPendingTaskFragmentEvents.keyAt(i)); + dispatchPendingEvents(state, mPendingTaskFragmentEvents.valueAt(i)); } } - void dispatchPendingEvents(@NonNull ITaskFragmentOrganizer organizer, + void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state, @NonNull List<PendingTaskFragmentEvent> pendingEvents) { if (pendingEvents.isEmpty()) { return; @@ -817,7 +880,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr if (mTmpTaskSet.add(task)) { // Make sure the organizer know about the Task config. transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( - PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, state.mOrganizer) .setTask(task) .build())); } @@ -825,7 +888,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr transaction.addChange(prepareChange(event)); } mTmpTaskSet.clear(); - dispatchTransactionInfo(organizer, transaction); + state.dispatchTransaction(transaction); pendingEvents.removeAll(candidateEvents); } @@ -855,6 +918,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer(); + final TaskFragmentOrganizerState state = validateAndGetState(organizer); final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); // Make sure the organizer know about the Task config. transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( @@ -862,22 +926,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setTask(taskFragment.getTask()) .build())); transaction.addChange(prepareChange(event)); - dispatchTransactionInfo(event.mTaskFragmentOrg, transaction); + state.dispatchTransaction(transaction); mPendingTaskFragmentEvents.get(organizer.asBinder()).remove(event); } - private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer, - @NonNull TaskFragmentTransaction transaction) { - if (transaction.isEmpty()) { - return; - } - try { - organizer.onTransactionReady(transaction); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); - } - } - @Nullable private TaskFragmentTransaction.Change prepareChange( @NonNull PendingTaskFragmentEvent event) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 6e0d411caca4..2d3e437bed60 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -31,7 +31,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -66,6 +72,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.os.SystemClock; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; @@ -73,6 +80,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.SurfaceControl; import android.view.WindowManager; +import android.view.animation.Animation; import android.window.RemoteTransition; import android.window.TransitionInfo; @@ -1058,9 +1066,36 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc, @TransitionType int transit, @TransitionFlags int flags) { + if ((transit == TRANSIT_KEYGUARD_GOING_AWAY + || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) + && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { + Animation anim = mController.mAtm.mWindowManager.mPolicy + .createKeyguardWallpaperExit( + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); + if (anim != null) { + anim.scaleCurrentDuration( + mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked()); + dc.mWallpaperController.startWallpaperAnimation(anim); + } + } + dc.startKeyguardExitOnNonAppWindows( + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); + if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { + // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI + // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't + // need to call IKeyguardService#keyguardGoingAway here. + mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation( + SystemClock.uptimeMillis(), 0 /* duration */); + } + } if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange( - false /* notify */); + true /* keyguardOccludingStarted */); } } @@ -1780,6 +1815,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** This undoes one call to {@link #deferTransitionReady}. */ void continueTransitionReady() { --mReadyTracker.mDeferReadyDepth; + // Apply ready in case it is waiting for the previous defer call. + applyReady(); } /** diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 010c509ddd8a..846aa3e3739a 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -637,9 +637,11 @@ class TransitionController { } void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) { + final boolean keyguardGoingAway = info.isKeyguardGoingAway(); for (int i = 0; i < mLegacyListeners.size(); ++i) { // TODO(shell-transitions): handle (un)occlude transition. - mLegacyListeners.get(i).onAppTransitionStartingLocked( + mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, + false /* keyguardOcclude */, 0 /* durationHint */, SystemClock.uptimeMillis() + statusBarTransitionDelay, AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); } @@ -654,7 +656,7 @@ class TransitionController { void dispatchLegacyAppTransitionCancelled() { for (int i = 0; i < mLegacyListeners.size(); ++i) { mLegacyListeners.get(i).onAppTransitionCancelledLocked( - false /* keyguardGoingAwayCancelled */, false /* keyguardOccludedCancelled */); + false /* keyguardGoingAway */); } } diff --git a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java index 6a878b983543..f376e8b1f9ed 100644 --- a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java +++ b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java @@ -29,13 +29,11 @@ import android.widget.CheckBox; import com.android.internal.R; import com.android.server.utils.AppInstallerUtil; -public class UnsupportedCompileSdkDialog { - private final AlertDialog mDialog; - private final String mPackageName; +class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog { - public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context, + UnsupportedCompileSdkDialog(final AppWarnings manager, Context context, ApplicationInfo appInfo) { - mPackageName = appInfo.packageName; + super(manager, appInfo.packageName); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -72,16 +70,4 @@ public class UnsupportedCompileSdkDialog { alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked)); } - - public String getPackageName() { - return mPackageName; - } - - public void show() { - mDialog.show(); - } - - public void dismiss() { - mDialog.dismiss(); - } } diff --git a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java index 4a800c41fdeb..b11c22de4286 100644 --- a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java +++ b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import com.android.internal.R; - import android.app.AlertDialog; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -27,13 +25,13 @@ import android.view.Window; import android.view.WindowManager; import android.widget.CheckBox; -public class UnsupportedDisplaySizeDialog { - private final AlertDialog mDialog; - private final String mPackageName; +import com.android.internal.R; - public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context, +class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog { + + UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context, ApplicationInfo appInfo) { - mPackageName = appInfo.packageName; + super(manager, appInfo.packageName); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -63,16 +61,4 @@ public class UnsupportedDisplaySizeDialog { alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked)); } - - public String getPackageName() { - return mPackageName; - } - - public void show() { - mDialog.show(); - } - - public void dismiss() { - mDialog.dismiss(); - } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 4b5b6dad4fc4..a71c3866ba38 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -220,13 +220,9 @@ public abstract class WindowManagerInternal { /** * Called when a pending app transition gets cancelled. * - * @param keyguardGoingAwayCancelled {@code true} if keyguard going away transition was - * cancelled. - * @param keyguardOccludedCancelled {@code true} if keyguard (un)occluded transition was - * cancelled. + * @param keyguardGoingAway true if keyguard going away transition got cancelled. */ - public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) {} + public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {} /** * Called when an app transition is timed out. @@ -236,6 +232,9 @@ public abstract class WindowManagerInternal { /** * Called when an app transition gets started * + * @param keyguardGoingAway true if keyguard going away transition is started. + * @param keyguardOccluding true if keyguard (un)occlude transition is started. + * @param duration the total duration of the transition * @param statusBarAnimationStartTime the desired start time for all visual animations in * the status bar caused by this app transition in uptime millis * @param statusBarAnimationDuration the duration for all visual animations in the status @@ -246,7 +245,8 @@ public abstract class WindowManagerInternal { * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER}, * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}. */ - public int onAppTransitionStartingLocked(long statusBarAnimationStartTime, + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { return 0; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 298f93d75e7a..22411bb068a0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -425,6 +425,32 @@ public class WindowManagerService extends IWindowManager.Stub SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false); /** + * Run Keyguard animation as remote animation in System UI instead of local animation in + * the server process. + * + * 0: Runs all keyguard animation as local animation + * 1: Only runs keyguard going away animation as remote animation + * 2: Runs all keyguard animation as remote animation + */ + private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY = + "persist.wm.enable_remote_keyguard_animation"; + + private static final int sEnableRemoteKeyguardAnimation = + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2); + + /** + * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY + */ + public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = + sEnableRemoteKeyguardAnimation >= 1; + + /** + * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY + */ + public static final boolean sEnableRemoteKeyguardOccludeAnimation = + sEnableRemoteKeyguardAnimation >= 2; + + /** * Allows a fullscreen windowing mode activity to launch in its desired orientation directly * when the display has different orientation. */ @@ -1092,8 +1118,7 @@ public class WindowManagerService extends IWindowManager.Stub = new WindowManagerInternal.AppTransitionListener() { @Override - public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled, - boolean keyguardOccludedCancelled) { + public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { } @Override diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4f03264b1556..68b1d354272d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -147,7 +147,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); - mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm); + mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm, this); } void setWindowManager(WindowManagerService wms) { diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 85671105ec25..9abf107c780a 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -11,7 +11,7 @@ per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, # BatteryStats per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS -per-file Android.bp = file:platform/build/soong:/OWNERS +per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} per-file com_android_server_Usb* = file:/services/usb/OWNERS per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java index 054181dfa728..0305c35b1828 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java @@ -671,6 +671,21 @@ class DevicePolicyData { if (mFactoryResetReason != null) { pw.print("mFactoryResetReason="); pw.println(mFactoryResetReason); } + if (mDelegationMap.size() != 0) { + pw.println("mDelegationMap="); + pw.increaseIndent(); + for (int i = 0; i < mDelegationMap.size(); i++) { + List<String> delegationScopes = mDelegationMap.valueAt(i); + pw.println(mDelegationMap.keyAt(i) + "[size=" + delegationScopes.size() + + "]"); + pw.increaseIndent(); + for (int j = 0; j < delegationScopes.size(); j++) { + pw.println(j + ": " + delegationScopes.get(j)); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } pw.decreaseIndent(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c1c7bceaebd9..4f67f1d3aa28 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5786,29 +5786,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @VisibleForTesting public void enforceCallerCanRequestDeviceIdAttestation(CallerIdentity caller) throws SecurityException { - /** - * First check if there's a profile owner because the device could be in COMP mode (where - * there's a device owner and profile owner on the same device). - * If the caller is from the work profile, then it must be the PO or the delegate, and - * it must have the right permission to access device identifiers. - */ - int callerUserId = caller.getUserId(); - if (hasProfileOwner(callerUserId)) { - // Make sure that the caller is the profile owner or delegate. - Preconditions.checkCallAuthorization(canInstallCertificates(caller)); - // Verify that the managed profile is on an organization-owned device (or is affiliated - // with the device owner user) and as such the profile owner can access Device IDs. - if (isProfileOwnerOfOrganizationOwnedDevice(callerUserId) - || isUserAffiliatedWithDevice(callerUserId)) { - return; - } - throw new SecurityException( - "Profile Owner is not allowed to access Device IDs."); - } - - // If not, fall back to the device owner check. - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + Preconditions.checkCallAuthorization(hasDeviceIdAccessUnchecked(caller.getPackageName(), + caller.getUid())); } @VisibleForTesting @@ -5856,7 +5835,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean deviceIdAttestationRequired = attestationUtilsFlags != null; KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec(); final String alias = keySpec.getKeystoreAlias(); - Preconditions.checkStringNotEmpty(alias, "Empty alias provided"); Preconditions.checkArgument( !deviceIdAttestationRequired || keySpec.getAttestationChallenge() != null, @@ -8363,6 +8341,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + PackageManager.FEATURE_DEVICE_ADMIN + " feature."); } + // TODO(b/240562946): Remove owner name from API parameters. @Override public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId, boolean setProfileOwnerOnCurrentUserIfNecessary) { @@ -8397,7 +8376,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } - mOwners.setDeviceOwner(admin, ownerName, userId); + mOwners.setDeviceOwner(admin, userId); mOwners.writeDeviceOwner(); setDeviceOwnershipSystemPropertyLocked(); @@ -8649,6 +8628,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + // TODO(b/240562946): Remove api as owner name is not used. /** * Returns the "name" of the device owner. It'll work for non-DO users too, but requires * MANAGE_USERS. @@ -8819,6 +8799,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } + // TODO(b/240562946): Remove owner name from API parameters. @Override public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) { if (!mHasFeature) { @@ -8866,7 +8847,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Shutting down backup manager service permanently. toggleBackupServiceActive(userHandle, /* makeActive= */ false); - mOwners.setProfileOwner(who, ownerName, userHandle); + mOwners.setProfileOwner(who, userHandle); mOwners.writeProfileOwner(userHandle); Slogf.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle); @@ -9337,6 +9318,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return who.getPackageName().equals(configPackage); } + // TODO(b/240562946): Remove api as owner name is not used. @Override public String getProfileOwnerName(int userHandle) { if (!mHasFeature) { @@ -9389,26 +9371,33 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!hasPermission(permission.READ_PHONE_STATE, pid, uid)) { return false; } + return hasDeviceIdAccessUnchecked(packageName, uid); + } - // Allow access to the device owner or delegate cert installer or profile owner of an - // affiliated user + /** + * Check if caller is device owner, delegate cert installer or profile owner of + * affiliated user. Or if caller is profile owner for a specified user or delegate cert + * installer on an organization-owned device. + */ + private boolean hasDeviceIdAccessUnchecked(String packageName, int uid) { + // Is the caller a device owner, delegate cert installer or profile owner of an + // affiliated user. ComponentName deviceOwner = getDeviceOwnerComponent(true); if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { return true; } final int userId = UserHandle.getUserId(uid); - // Allow access to the profile owner for the specified user, or delegate cert installer - // But only if this is an organization-owned device. + // Is the caller the profile owner for the specified user, or delegate cert installer on an + // organization-owned device. ComponentName profileOwner = getProfileOwnerAsUser(userId); final boolean isCallerProfileOwnerOrDelegate = profileOwner != null && (profileOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) || isUserAffiliatedWithDevice(userId))) { return true; } - return false; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 08bd3e4c7c47..3b46d5238e6a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -193,12 +193,6 @@ class Owners { } } - String getDeviceOwnerName() { - synchronized (mData) { - return mData.mDeviceOwner != null ? mData.mDeviceOwner.name : null; - } - } - ComponentName getDeviceOwnerComponent() { synchronized (mData) { return mData.mDeviceOwner != null ? mData.mDeviceOwner.admin : null; @@ -217,7 +211,7 @@ class Owners { } } - void setDeviceOwner(ComponentName admin, String ownerName, int userId) { + void setDeviceOwner(ComponentName admin, int userId) { if (userId < 0) { Slog.e(TAG, "Invalid user id for device owner user: " + userId); return; @@ -226,7 +220,7 @@ class Owners { // A device owner is allowed to access device identifiers. Even though this flag // is not currently checked for device owner, it is set to true here so that it is // semantically compatible with the meaning of this flag. - mData.mDeviceOwner = new OwnerInfo(ownerName, admin, /* remoteBugreportUri =*/ null, + mData.mDeviceOwner = new OwnerInfo(admin, /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ true); mData.mDeviceOwnerUserId = userId; @@ -248,10 +242,10 @@ class Owners { } } - void setProfileOwner(ComponentName admin, String ownerName, int userId) { + void setProfileOwner(ComponentName admin, int userId) { synchronized (mData) { // For a newly set PO, there's no need for migration. - mData.mProfileOwners.put(userId, new OwnerInfo(ownerName, admin, + mData.mProfileOwners.put(userId, new OwnerInfo(admin, /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ false)); mUserManagerInternal.setUserManaged(userId, true); @@ -270,7 +264,7 @@ class Owners { void transferProfileOwner(ComponentName target, int userId) { synchronized (mData) { final OwnerInfo ownerInfo = mData.mProfileOwners.get(userId); - final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target, + final OwnerInfo newOwnerInfo = new OwnerInfo(target, ownerInfo.remoteBugreportUri, ownerInfo.remoteBugreportHash, ownerInfo.isOrganizationOwnedDevice); mData.mProfileOwners.put(userId, newOwnerInfo); @@ -282,9 +276,7 @@ class Owners { synchronized (mData) { Integer previousDeviceOwnerType = mData.mDeviceOwnerTypes.remove( mData.mDeviceOwner.packageName); - // We don't set a name because it's not used anyway. - // See DevicePolicyManagerService#getDeviceOwnerName - mData.mDeviceOwner = new OwnerInfo(null, target, + mData.mDeviceOwner = new OwnerInfo(target, mData.mDeviceOwner.remoteBugreportUri, mData.mDeviceOwner.remoteBugreportHash, mData.mDeviceOwner.isOrganizationOwnedDevice); @@ -305,13 +297,6 @@ class Owners { } } - String getProfileOwnerName(int userId) { - synchronized (mData) { - OwnerInfo profileOwner = mData.mProfileOwners.get(userId); - return profileOwner != null ? profileOwner.name : null; - } - } - String getProfileOwnerPackage(int userId) { synchronized (mData) { OwnerInfo profileOwner = mData.mProfileOwners.get(userId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 694842034d1f..2ab54644fd18 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -493,16 +493,14 @@ class OwnersData { } static class OwnerInfo { - public final String name; public final String packageName; public final ComponentName admin; public String remoteBugreportUri; public String remoteBugreportHash; public boolean isOrganizationOwnedDevice; - OwnerInfo(String name, ComponentName admin, String remoteBugreportUri, + OwnerInfo(ComponentName admin, String remoteBugreportUri, String remoteBugreportHash, boolean isOrganizationOwnedDevice) { - this.name = name; this.admin = admin; this.packageName = admin.getPackageName(); this.remoteBugreportUri = remoteBugreportUri; @@ -512,9 +510,6 @@ class OwnersData { public void writeToXml(TypedXmlSerializer out, String tag) throws IOException { out.startTag(null, tag); - if (name != null) { - out.attribute(null, ATTR_NAME, name); - } if (admin != null) { out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString()); } @@ -532,7 +527,6 @@ class OwnersData { } public static OwnerInfo readFromXml(TypedXmlPullParser parser) { - final String name = parser.getAttributeValue(null, ATTR_NAME); final String componentName = parser.getAttributeValue(null, ATTR_COMPONENT_NAME); final String remoteBugreportUri = parser.getAttributeValue(null, ATTR_REMOTE_BUGREPORT_URI); @@ -556,13 +550,11 @@ class OwnersData { return null; } - return new OwnerInfo( - name, admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice); + return new OwnerInfo(admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice); } public void dump(IndentingPrintWriter pw) { pw.println("admin=" + admin); - pw.println("name=" + name); pw.println("package=" + packageName); pw.println("isOrganizationOwnedDevice=" + isOrganizationOwnedDevice); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 1b39add3b421..9d039bd09a2f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -209,7 +209,8 @@ public class FlexibilityControllerTest { public void testOnConstantsUpdated_DeadlineProximity() { JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0)); setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE); - mFlexibilityController.mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); + mFlexibilityController.mFlexibilityAlarmQueue + .scheduleDropNumConstraintsAlarm(js, FROZEN_TIME); assertEquals(0, js.getNumRequiredFlexibleConstraints()); } @@ -336,26 +337,31 @@ public class FlexibilityControllerTest { @Test public void testCurPercent() { long deadline = 1000; + long nowElapsed; JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline); JobStatus js = createJobStatus("time", jb); assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); assertEquals(deadline + FROZEN_TIME, mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME)); + nowElapsed = 600 + FROZEN_TIME; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME), ZoneOffset.UTC); - assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); + assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); + nowElapsed = 1400; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC); - assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); + assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); + nowElapsed = 950 + FROZEN_TIME; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME), ZoneOffset.UTC); - assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); + assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); + nowElapsed = FROZEN_TIME; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); long delay = 100; deadline = 1100; jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay); @@ -366,18 +372,21 @@ public class FlexibilityControllerTest { assertEquals(deadline + FROZEN_TIME, mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay)); + nowElapsed = 600 + FROZEN_TIME + delay; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME + delay), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); + nowElapsed = 1400; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC); - assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); + assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); + nowElapsed = 950 + FROZEN_TIME + delay; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME + delay), ZoneOffset.UTC); - assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); + assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); } @Test @@ -491,19 +500,19 @@ public class FlexibilityControllerTest { assertEquals(3, trackedJobs.get(2).size()); assertEquals(0, trackedJobs.get(3).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -1); + flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(0, trackedJobs.get(0).size()); assertEquals(1, trackedJobs.get(1).size()); assertEquals(2, trackedJobs.get(2).size()); assertEquals(0, trackedJobs.get(3).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -1); + flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(2, trackedJobs.get(2).size()); assertEquals(0, trackedJobs.get(3).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -1); + flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(0, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(2, trackedJobs.get(2).size()); @@ -515,24 +524,25 @@ public class FlexibilityControllerTest { assertEquals(1, trackedJobs.get(2).size()); assertEquals(0, trackedJobs.get(3).size()); - flexTracker.resetJobNumDroppedConstraints(jobs[0]); + flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME); assertEquals(0, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(2, trackedJobs.get(2).size()); assertEquals(0, trackedJobs.get(3).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -2); + flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(1, trackedJobs.get(2).size()); assertEquals(0, trackedJobs.get(3).size()); + final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2) + + HOUR_IN_MILLIS); JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2) - + HOUR_IN_MILLIS), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - flexTracker.resetJobNumDroppedConstraints(jobs[0]); + flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed); assertEquals(0, trackedJobs.get(0).size()); assertEquals(1, trackedJobs.get(1).size()); assertEquals(1, trackedJobs.get(2).size()); @@ -615,13 +625,13 @@ public class FlexibilityControllerTest { @Test public void testSetConstraintSatisfied_Constraints() { - mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME); assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE)); - mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, FROZEN_TIME); assertTrue(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE)); - mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME); assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE)); } @@ -651,20 +661,21 @@ public class FlexibilityControllerTest { createJobStatus(String.valueOf(i), jb), null); } } - mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false); - mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false); - mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, false); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false, FROZEN_TIME); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_BATTERY_NOT_LOW, false, FROZEN_TIME); assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints); for (int i = 0; i < constraintCombinations.length; i++) { constraints = constraintCombinations[i]; mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, - (constraints & CONSTRAINT_CHARGING) != 0); + (constraints & CONSTRAINT_CHARGING) != 0, FROZEN_TIME); mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, - (constraints & CONSTRAINT_IDLE) != 0); + (constraints & CONSTRAINT_IDLE) != 0, FROZEN_TIME); mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, - (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0); + (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0, FROZEN_TIME); assertEquals(constraints, mFlexibilityController.mSatisfiedFlexibleConstraints); synchronized (mFlexibilityController.mLock) { @@ -679,6 +690,7 @@ public class FlexibilityControllerTest { JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L); JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb); js.adjustNumRequiredFlexibleConstraints(3); + long nowElapsed; mFlexibilityController.mFlexibilityTracker.add(js); @@ -687,45 +699,51 @@ public class FlexibilityControllerTest { assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); + + nowElapsed = 155L; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(155L), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1); + mFlexibilityController.mFlexibilityTracker + .adjustJobsRequiredConstraints(js, -1, nowElapsed); assertEquals(2, js.getNumRequiredFlexibleConstraints()); assertEquals(1, js.getNumDroppedFlexibleConstraints()); assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js); + mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); assertEquals(2, js.getNumRequiredFlexibleConstraints()); assertEquals(1, js.getNumDroppedFlexibleConstraints()); assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); + nowElapsed = 140L; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(140L), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js); + mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); assertEquals(3, js.getNumRequiredFlexibleConstraints()); assertEquals(0, js.getNumDroppedFlexibleConstraints()); assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); + nowElapsed = 175L; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(175), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js); + mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); assertEquals(0, js.getNumRequiredFlexibleConstraints()); assertEquals(3, js.getNumDroppedFlexibleConstraints()); + nowElapsed = 165L; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(165L), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js); + mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); assertEquals(1, js.getNumRequiredFlexibleConstraints()); assertEquals(2, js.getNumDroppedFlexibleConstraints()); @@ -745,12 +763,14 @@ public class FlexibilityControllerTest { mFlexibilityController.maybeStartTrackingJobLocked(js, null); + final long nowElapsed = 150L; JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(Instant.ofEpochMilli(150L), ZoneOffset.UTC); + Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated( jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE, - 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS); + 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS, + nowElapsed); assertEquals(150L, (long) mFlexibilityController.mPrefetchLifeCycleStart @@ -758,7 +778,7 @@ public class FlexibilityControllerTest { assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); assertEquals(1150L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L)); - assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js)); + assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME)); assertEquals(650L, mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js)); assertEquals(3, js.getNumRequiredFlexibleConstraints()); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 969389c27e70..bb477b18d5fa 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -490,9 +490,9 @@ public class PrefetchControllerTest { final PrefetchController.PrefetchChangedListener prefetchChangedListener = new PrefetchController.PrefetchChangedListener() { @Override - public void onPrefetchCacheUpdated( - ArraySet<JobStatus> jobs, int userId, String pkgName, - long prevEstimatedLaunchTime, long newEstimatedLaunchTime) { + public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, + int userId, String pkgName, long prevEstimatedLaunchTime, + long newEstimatedLaunchTime, long nowElapsed) { onPrefetchCacheChangedCalled[0] = true; } }; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java new file mode 100644 index 000000000000..903ed9082481 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; +import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.IBiometricService; +import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; +import android.hardware.face.IFaceService; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class FaceServiceRegistryTest { + + private static final int SENSOR_ID_1 = 1; + private static final int SENSOR_ID_2 = 2; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricService mBiometricService; + @Mock + private IFaceService mFaceService; + @Mock + private ServiceProvider mProvider1; + @Mock + private ServiceProvider mProvider2; + @Captor + private ArgumentCaptor<Integer> mIdCaptor; + @Captor + private ArgumentCaptor<Integer> mStrengthCaptor; + + private FaceSensorPropertiesInternal mProvider1Props; + private FaceSensorPropertiesInternal mProvider2Props; + private FaceServiceRegistry mRegistry; + + @Before + public void setup() { + mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1, + STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */, + List.of(), FaceSensorProperties.TYPE_RGB, + true /* supportsFace Detection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */); + mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2, + STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, + List.of(), FaceSensorProperties.TYPE_IR, + true /* supportsFace Detection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */); + + when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props)); + when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true); + when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props)); + when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true); + mRegistry = new FaceServiceRegistry(mFaceService, () -> mBiometricService); + } + + @Test + public void registersAllProviders() throws Exception { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2); + assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props); + verify(mBiometricService, times(2)).registerAuthenticator( + mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any()); + assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2); + assertThat(mStrengthCaptor.getAllValues()) + .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG); + } + + @Test + public void getsProviderById() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1); + assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2); + assertThat(mRegistry.getProviderForSensor(500)).isNull(); + } + + @Test + public void getsSingleProvider() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1)); + + assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1); + assertThat(mRegistry.getProviders()).containsExactly(mProvider1); + assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1); + } + + @Test + public void getSingleProviderFindsFirstWhenMultiple() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1); + } + + @Test + public void registersListenerBeforeAllRegistered() { + final List<FaceSensorPropertiesInternal> all = new ArrayList<>(); + mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FaceSensorPropertiesInternal> sensors) { + all.addAll(sensors); + } + }); + + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(all).containsExactly(mProvider1Props, mProvider2Props); + } + + @Test + public void registersListenerAfterAllRegistered() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + final List<FaceSensorPropertiesInternal> all = new ArrayList<>(); + mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FaceSensorPropertiesInternal> sensors) { + all.addAll(sensors); + } + }); + + assertThat(all).containsExactly(mProvider1Props, mProvider2Props); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java index 5f88c99b1d1e..0e30782eaece 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; + import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -24,38 +26,73 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricStateListener; +import android.hardware.biometrics.SensorPropertiesInternal; +import android.os.UserManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.BiometricServiceProvider; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.EnrollClient; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; @Presubmit @SmallTest public class BiometricStateCallbackTest { - private BiometricStateCallback mCallback; + private static final int USER_ID = 10; + private static final int SENSOR_ID = 2; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private BiometricStateCallback<FakeProvider, SensorPropertiesInternal> mCallback; @Mock - BiometricStateListener mBiometricStateListener; + private UserManager mUserManager; + @Mock + private BiometricStateListener mBiometricStateListener; + @Mock + private FakeProvider mFakeProvider; + + private SensorPropertiesInternal mFakeProviderProps; @Before public void setup() { - MockitoAnnotations.initMocks(this); - - mCallback = new BiometricStateCallback(); + mFakeProviderProps = new SensorPropertiesInternal(SENSOR_ID, STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, List.of(), + false /* resetLockoutRequiresHardwareAuthToken */, + false /* resetLockoutRequiresChallenge */); + when(mFakeProvider.getSensorProperties()).thenReturn(List.of(mFakeProviderProps)); + when(mFakeProvider.getSensorProperties(eq(SENSOR_ID))).thenReturn(mFakeProviderProps); + when(mFakeProvider.hasEnrollments(eq(SENSOR_ID), eq(USER_ID))).thenReturn(true); + when(mUserManager.getAliveUsers()).thenReturn( + List.of(new UserInfo(USER_ID, "name", 0))); + + mCallback = new BiometricStateCallback<>(mUserManager); mCallback.registerBiometricStateListener(mBiometricStateListener); } @Test + public void startNotifiesEnrollments() { + mCallback.start(List.of(mFakeProvider)); + + verify(mBiometricStateListener).onEnrollmentsChanged(eq(USER_ID), eq(SENSOR_ID), eq(true)); + } + + @Test public void testNoEnrollmentsToEnrollments_callbackNotified() { testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */, true /* expectCallback */, true /* expectedCallbackValue */); @@ -102,4 +139,6 @@ public class BiometricStateCallbackTest { verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(), anyBoolean()); } + + private interface FakeProvider extends BiometricServiceProvider<SensorPropertiesInternal> {} } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java new file mode 100644 index 000000000000..13c3f64fec93 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java @@ -0,0 +1,168 @@ +/* + * 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.biometrics.sensors.fingerprint; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; +import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.IBiometricService; +import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; +import android.hardware.fingerprint.IFingerprintService; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class FingerprintServiceRegistryTest { + + private static final int SENSOR_ID_1 = 1; + private static final int SENSOR_ID_2 = 2; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricService mBiometricService; + @Mock + private IFingerprintService mFingerprintService; + @Mock + private ServiceProvider mProvider1; + @Mock + private ServiceProvider mProvider2; + @Captor + private ArgumentCaptor<Integer> mIdCaptor; + @Captor + private ArgumentCaptor<Integer> mStrengthCaptor; + + private FingerprintSensorPropertiesInternal mProvider1Props; + private FingerprintSensorPropertiesInternal mProvider2Props; + private FingerprintServiceRegistry mRegistry; + + @Before + public void setup() { + mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1, + STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */, + List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + false /* resetLockoutRequiresHardwareAuthToken */); + mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2, + STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, + List.of(), FingerprintSensorProperties.TYPE_UNKNOWN, + false /* resetLockoutRequiresHardwareAuthToken */); + + when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props)); + when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true); + when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props)); + when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true); + mRegistry = new FingerprintServiceRegistry(mFingerprintService, () -> mBiometricService); + } + + @Test + public void registersAllProviders() throws Exception { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2); + assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props); + verify(mBiometricService, times(2)).registerAuthenticator( + mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any()); + assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2); + assertThat(mStrengthCaptor.getAllValues()) + .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG); + } + + @Test + public void getsProviderById() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1); + assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2); + assertThat(mRegistry.getProviderForSensor(500)).isNull(); + } + + @Test + public void getsSingleProvider() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1)); + + assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1); + assertThat(mRegistry.getProviders()).containsExactly(mProvider1); + assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1); + } + + @Test + public void getSingleProviderFindsFirstWhenMultiple() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1); + } + + @Test + public void registersListenerBeforeAllRegistered() { + final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>(); + mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> sensors) { + all.addAll(sensors); + } + }); + + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + assertThat(all).containsExactly(mProvider1Props, mProvider2Props); + } + + @Test + public void registersListenerAfterAllRegistered() { + mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2)); + + final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>(); + mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> sensors) { + all.addAll(sensors); + } + }); + + assertThat(all).containsExactly(mProvider1Props, mProvider2Props); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java index ca3677e3b5ff..a4048a27dfb5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java @@ -32,7 +32,8 @@ import android.hardware.biometrics.fingerprint.FingerprintSensorType; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorLocation; import android.hardware.biometrics.fingerprint.SensorProps; -import android.os.RemoteException; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.testing.TestableContext; @@ -52,6 +53,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @Presubmit @SmallTest @@ -94,9 +97,12 @@ public class FingerprintServiceTest { mContext.getTestablePermissions().setPermission( USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED); + } + private void initServiceWith(String... aidlInstances) { mService = new FingerprintService(mContext, mBiometricContext, () -> mIBiometricService, + () -> aidlInstances, (fqName) -> { if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault; if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual; @@ -105,29 +111,50 @@ public class FingerprintServiceTest { } @Test - public void registerAuthenticators_defaultOnly() throws RemoteException { - mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of()); + public void registerAuthenticators_defaultOnly() throws Exception { + initServiceWith(NAME_DEFAULT, NAME_VIRTUAL); + + mService.mServiceWrapper.registerAuthenticators(List.of()); + waitForRegistration(); verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any()); } @Test - public void registerAuthenticators_virtualOnly() throws RemoteException { + public void registerAuthenticators_virtualOnly() throws Exception { + initServiceWith(NAME_DEFAULT, NAME_VIRTUAL); Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext), Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1); - mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of()); + mService.mServiceWrapper.registerAuthenticators(List.of()); + waitForRegistration(); verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any()); } @Test - public void registerAuthenticators_virtualAlwaysWhenNoOther() throws RemoteException { - mService.registerAuthenticatorsForService(List.of(NAME_VIRTUAL), List.of()); + public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception { + initServiceWith(NAME_VIRTUAL); + + mService.mServiceWrapper.registerAuthenticators(List.of()); + waitForRegistration(); verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any()); } + private void waitForRegistration() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + mService.mServiceWrapper.addAuthenticatorsRegisteredCallback( + new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> sensors) { + latch.countDown(); + } + }); + latch.await(5, TimeUnit.SECONDS); + } + private static SensorProps createProps(int id, byte strength, byte type) { final SensorProps props = new SensorProps(); props.commonProps = new CommonProps(); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 34b40c716b4f..b1ad8ec1cb66 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -16,53 +16,79 @@ package com.android.server.pm; +import static android.os.UserManager.DISALLOW_USER_SWITCH; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.ActivityManager; +import android.app.PropertyInvalidatedCache; +import android.content.Context; import android.content.pm.UserInfo; import android.os.Bundle; import android.os.FileUtils; +import android.os.Looper; import android.os.Parcelable; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Postsubmit; import android.support.test.uiautomator.UiDevice; -import android.test.AndroidTestCase; -import android.text.TextUtils; import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; import java.io.IOException; import java.util.Arrays; +/** Test {@link UserManagerService} functionality. */ @Postsubmit -@SmallTest -public class UserManagerServiceTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class UserManagerServiceTest { private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["}; private File restrictionsFile; private int tempUserId = UserHandle.USER_NULL; + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private UserManagerService mUserManagerService; + + @Before + public void setup() throws Exception { + // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup + // TODO: Remove once UMS supports proper dependency injection + if (Looper.myLooper() == null) { + Looper.prepare(); + } + // Disable binder caches in this process. + PropertyInvalidatedCache.disableForTestMode(); + + LocalServices.removeServiceForTest(UserManagerInternal.class); + mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext()); - @Override - protected void setUp() throws Exception { - super.setUp(); restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); restrictionsFile.delete(); } - @Override - protected void tearDown() throws Exception { + @After + public void teardown() throws Exception { restrictionsFile.delete(); if (tempUserId != UserHandle.USER_NULL) { UserManager.get(mContext).removeUser(tempUserId); } - super.tearDown(); } + @Test public void testWriteReadApplicationRestrictions() throws IOException { AtomicFile atomicFile = new AtomicFile(restrictionsFile); Bundle bundle = createBundle(); UserManagerService.writeApplicationRestrictionsLAr(bundle, atomicFile); - assertTrue(atomicFile.getBaseFile().exists()); + assertThat(atomicFile.getBaseFile().exists()).isTrue(); String s = FileUtils.readTextFile(restrictionsFile, 10000, ""); System.out.println("restrictionsFile: " + s); bundle = UserManagerService.readApplicationRestrictionsLAr(atomicFile); @@ -70,22 +96,22 @@ public class UserManagerServiceTest extends AndroidTestCase { assertBundle(bundle); } + @Test public void testAddUserWithAccount() { UserManager um = UserManager.get(mContext); UserInfo user = um.createUser("Test User", 0); - assertNotNull(user); + assertThat(user).isNotNull(); tempUserId = user.id; String accountName = "Test Account"; um.setUserAccount(tempUserId, accountName); - assertEquals(accountName, um.getUserAccount(tempUserId)); + assertThat(um.getUserAccount(tempUserId)).isEqualTo(accountName); } + @Test public void testUserSystemPackageWhitelist() throws Exception { String cmd = "cmd user report-system-user-package-whitelist-problems --critical-only"; final String result = runShellCommand(cmd); - if (!TextUtils.isEmpty(result)) { - fail("Command '" + cmd + " reported errors:\n" + result); - } + assertThat(result).isEmpty(); } private Bundle createBundle() { @@ -114,26 +140,141 @@ public class UserManagerServiceTest extends AndroidTestCase { } private void assertBundle(Bundle bundle) { - assertFalse(bundle.getBoolean("boolean_0")); - assertTrue(bundle.getBoolean("boolean_1")); - assertEquals(100, bundle.getInt("integer")); - assertEquals("", bundle.getString("empty")); - assertEquals("text", bundle.getString("string")); - assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]"))); + assertThat(bundle.getBoolean("boolean_0")).isFalse(); + assertThat(bundle.getBoolean("boolean_1")).isTrue(); + assertThat(bundle.getInt("integer")).isEqualTo(100); + assertThat(bundle.getString("empty")).isEqualTo(""); + assertThat(bundle.getString("string")).isEqualTo("text"); + assertThat(Arrays.asList(bundle.getStringArray("string[]"))) + .isEqualTo(Arrays.asList(STRING_ARRAY)); Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array"); - assertEquals(2, bundle_array.length); + assertThat(bundle_array.length).isEqualTo(2); Bundle bundle1 = (Bundle) bundle_array[0]; - assertEquals("bundle_array_string", bundle1.getString("bundle_array_string")); - assertNotNull(bundle1.getBundle("bundle_array_bundle")); + assertThat(bundle1.getString("bundle_array_string")) + .isEqualTo("bundle_array_string"); + assertThat(bundle1.getBundle("bundle_array_bundle")).isNotNull(); Bundle bundle2 = (Bundle) bundle_array[1]; - assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2")); + assertThat(bundle2.getString("bundle_array_string2")) + .isEqualTo("bundle_array_string2"); Bundle childBundle = bundle.getBundle("bundle"); - assertEquals("bundle_string", childBundle.getString("bundle_string")); - assertEquals(1, childBundle.getInt("bundle_int")); + assertThat(childBundle.getString("bundle_string")) + .isEqualTo("bundle_string"); + assertThat(childBundle.getInt("bundle_int")).isEqualTo(1); + } + + @Test + public void assertHasUserRestriction() throws Exception { + int userId = ActivityManager.getCurrentUser(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId); + assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isTrue(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId); + assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isFalse(); + } + + @Test + public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception { + int userId = ActivityManager.getCurrentUser(); + resetUserSwitcherEnabled(); + + setUserSwitch(false); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setUserSwitch(true); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + @Test + public void assertIsUserSwitcherEnabledOnMaxSupportedUsers() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setMaxSupportedUsers(1); + + assertThat(UserManager.supportsMultipleUsers()).isFalse(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setMaxSupportedUsers(8); + + assertThat(UserManager.supportsMultipleUsers()).isTrue(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + + @Test + public void assertIsUserSwitcherEnabledOnShowMultiuserUI() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setShowMultiuserUI(false); + + assertThat(UserManager.supportsMultipleUsers()).isFalse(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setShowMultiuserUI(true); + + assertThat(UserManager.supportsMultipleUsers()).isTrue(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + @Test + public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception { + int userId = ActivityManager.getCurrentUser(); + resetUserSwitcherEnabled(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + @Test + public void assertIsUserSwitcherEnabledOnDemoMode() throws Exception { + int userId = ActivityManager.getCurrentUser(); + resetUserSwitcherEnabled(); + + setDeviceDemoMode(true); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setDeviceDemoMode(false); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + private void resetUserSwitcherEnabled() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setUserSwitch(true); + setShowMultiuserUI(true); + setDeviceDemoMode(false); + setMaxSupportedUsers(8); + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId); + } + + private void setUserSwitch(boolean enabled) { + android.provider.Settings.Global.putInt(mContext.getContentResolver(), + android.provider.Settings.Global.USER_SWITCHER_ENABLED, enabled ? 1 : 0); } + private void setDeviceDemoMode(boolean enabled) { + android.provider.Settings.Global.putInt(mContext.getContentResolver(), + android.provider.Settings.Global.DEVICE_DEMO_MODE, enabled ? 1 : 0); + } + + private static String runShellCommand(String cmd) throws Exception { return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) .executeShellCommand(cmd); } + + private static String setSystemProperty(String name, String value) throws Exception { + final String oldValue = runShellCommand("getprop " + name); + assertThat(runShellCommand("setprop " + name + " " + value)) + .isEqualTo(""); + return oldValue; + } + + private static void setMaxSupportedUsers(int max) throws Exception { + setSystemProperty("fw.max_users", String.valueOf(max)); + } + + public static void setShowMultiuserUI(boolean show) throws Exception { + setSystemProperty("fw.show_multiuserui", String.valueOf(show)); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 5c9348525861..61a7f3853746 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -28,7 +28,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistory; -import com.android.internal.os.Clock; import org.junit.Before; import org.junit.Test; @@ -50,14 +49,13 @@ public class BatteryStatsHistoryTest { private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; private File mHistoryDir; - private final Clock mClock = new MockClock(); @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getContext(); mSystemDir = context.getDataDir(); - mHistoryDir = new File(mSystemDir, "battery-history"); + mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR); String[] files = mHistoryDir.list(); if (files != null) { for (int i = 0; i < files.length; i++) { @@ -69,8 +67,8 @@ public class BatteryStatsHistoryTest { @Test public void testConstruct() { - BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - null, mClock); + BatteryStatsHistory history = + new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32); createActiveFile(history); verifyFileNumbers(history, Arrays.asList(0)); verifyActiveFile(history, "0.bin"); @@ -78,8 +76,8 @@ public class BatteryStatsHistoryTest { @Test public void testStartNextFile() { - BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - null, mClock); + BatteryStatsHistory history = + new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32); List<Integer> fileList = new ArrayList<>(); fileList.add(0); createActiveFile(history); @@ -116,13 +114,13 @@ public class BatteryStatsHistoryTest { assertEquals(0, history.getHistoryUsedSize()); // create a new BatteryStatsHistory object, it will pick up existing history files. - BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - null, mClock); - // verify constructor can pick up all files from file system. + BatteryStatsHistory history2 = + new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32); + // verify construct can pick up all files from file system. verifyFileNumbers(history2, fileList); verifyActiveFile(history2, "33.bin"); - history2.reset(); + history2.resetAllFiles(); createActiveFile(history2); // verify all existing files are deleted. for (int i = 2; i < 33; ++i) { diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 570b2ee617f5..713e78638b95 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -63,7 +63,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { MockBatteryStatsImpl(Clock clock, File historyDirectory) { super(clock, historyDirectory); initTimersAndCounters(); - setMaxHistoryBuffer(128 * 1024); setExternalStatsSyncLocked(mExternalStatsSync); informThatAllExternalStatsAreFlushed(); @@ -105,6 +104,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return mForceOnBattery ? true : super.isOnBattery(); } + public void forceRecordAllHistory() { + mHaveBatteryLevel = true; + mRecordingHistory = true; + mRecordAllHistory = true; + } + public TimeBase getOnBatteryBackgroundTimeBase(int uid) { return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase; } @@ -196,14 +201,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { @GuardedBy("this") public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) { mConstants.MAX_HISTORY_FILES = maxHistoryFiles; - mConstants.onChange(); return this; } @GuardedBy("this") public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) { mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer; - mConstants.onChange(); return this; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index d62ac99e530b..598a22bbde39 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -93,7 +93,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.Parcel; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; @@ -2448,35 +2447,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testGetNotificationChannelGroup() throws Exception { - NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted"); - NotificationChannel base = - new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT); - base.setGroup("not"); - NotificationChannel convo = - new NotificationChannel("convo", "belongs to notDeleted", IMPORTANCE_DEFAULT); - convo.setGroup("not"); - convo.setConversationId("not deleted", "banana"); - - mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, base, true, false); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, convo, true, false); - mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true); - - NotificationChannelGroup g - = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1); - Parcel parcel = Parcel.obtain(); - g.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - - NotificationChannelGroup g2 - = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1); - Parcel parcel2 = Parcel.obtain(); - g2.writeToParcel(parcel2, 0); - parcel2.setDataPosition(0); - } - - @Test public void testOnUserRemoved() throws Exception { int[] user0Uids = {98, 235, 16, 3782}; int[] user1Uids = new int[user0Uids.length]; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java index c12f0a965146..d72cfc70fc02 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java @@ -17,7 +17,9 @@ package com.android.server.notification; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; @@ -30,6 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.NotificationChannel; import android.app.Person; import android.content.ContentProvider; import android.content.ContentResolver; @@ -39,8 +42,11 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserManager; import android.provider.ContactsContract; +import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableString; +import android.util.ArraySet; +import android.util.LruCache; import androidx.test.runner.AndroidJUnit4; @@ -323,6 +329,69 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { isNull()); // sort order } + @Test + public void testValidatePeople_needsLookupWhenNoCache() { + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final NotificationUsageStats mockNotificationUsageStats = + mock(NotificationUsageStats.class); + + // Create validator with empty cache + ValidateNotificationPeople vnp = new ValidateNotificationPeople(); + LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5); + vnp.initForTests(mockContext, mockNotificationUsageStats, cache); + + NotificationRecord record = getNotificationRecord(); + String[] callNumber = new String[]{"tel:12345678910"}; + setNotificationPeople(record, callNumber); + + // Returned ranking reconsideration not null indicates that there is a lookup to be done + RankingReconsideration rr = vnp.validatePeople(mockContext, record); + assertNotNull(rr); + } + + @Test + public void testValidatePeople_noLookupWhenCached_andPopulatesContactInfo() { + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + when(mockContext.getUserId()).thenReturn(1); + final NotificationUsageStats mockNotificationUsageStats = + mock(NotificationUsageStats.class); + + // Information to be passed in & returned from the lookup result + String lookup = "lookup:contactinfohere"; + String lookupTel = "16175551234"; + float affinity = 0.7f; + + // Create a fake LookupResult for the data we'll pass in + LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5); + ValidateNotificationPeople.LookupResult lr = + mock(ValidateNotificationPeople.LookupResult.class); + when(lr.getAffinity()).thenReturn(affinity); + when(lr.getPhoneNumbers()).thenReturn(new ArraySet<>(new String[]{lookupTel})); + when(lr.isExpired()).thenReturn(false); + cache.put(ValidateNotificationPeople.getCacheKey(1, lookup), lr); + + // Create validator with the established cache + ValidateNotificationPeople vnp = new ValidateNotificationPeople(); + vnp.initForTests(mockContext, mockNotificationUsageStats, cache); + + NotificationRecord record = getNotificationRecord(); + String[] peopleInfo = new String[]{lookup}; + setNotificationPeople(record, peopleInfo); + + // Returned ranking reconsideration null indicates that there is no pending work to be done + RankingReconsideration rr = vnp.validatePeople(mockContext, record); + assertNull(rr); + + // Confirm that the affinity & phone number made it into our record + assertEquals(affinity, record.getContactAffinity(), 1e-8); + assertNotNull(record.getPhoneNumbers()); + assertTrue(record.getPhoneNumbers().contains(lookupTel)); + } + // Creates a cursor that points to one item of Contacts data with the specified // columns. private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) { @@ -365,4 +434,17 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { String resultString = Arrays.toString(result); assertEquals(message + ": arrays differ", expectedString, resultString); } + + private NotificationRecord getNotificationRecord() { + StatusBarNotification sbn = mock(StatusBarNotification.class); + Notification notification = mock(Notification.class); + when(sbn.getNotification()).thenReturn(notification); + return new NotificationRecord(mContext, sbn, mock(NotificationChannel.class)); + } + + private void setNotificationPeople(NotificationRecord r, String[] people) { + Bundle extras = new Bundle(); + extras.putObject(Notification.EXTRA_PEOPLE_LIST, people); + r.getSbn().getNotification().extras = extras; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 75ecfd870eb2..d5e336b1cf2f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -228,7 +228,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { mAtm.getTaskChangeNotificationController(); spyOn(taskChangeNotifier); - mAtm.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA"); + mAtm.setLastResumedActivityUncheckLocked(fullScreenActivityA, "resumeA"); verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */, eq(true) /* focused */); reset(taskChangeNotifier); @@ -237,7 +237,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { .build(); final Task taskB = fullScreenActivityB.getTask(); - mAtm.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB"); + mAtm.setLastResumedActivityUncheckLocked(fullScreenActivityB, "resumeB"); verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */, eq(false) /* focused */); verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */, @@ -295,6 +295,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { activity1.moveFocusableActivityToTop("test"); assertEquals(activity1.getUid(), pendingTopUid[0]); verify(mAtm).updateOomAdj(); + verify(mAtm).setLastResumedActivityUncheckLocked(any(), eq("test")); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 88e58eab58aa..8546763aebec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -281,7 +281,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); // Simulate the app transition finishing - mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0); + mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0); verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index da72030b313d..9274eb3f1490 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -25,6 +25,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; @@ -46,7 +47,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -65,6 +65,7 @@ import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizerToken; +import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; @@ -90,6 +91,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private TaskFragmentOrganizerController mController; private WindowOrganizerController mWindowOrganizerController; + private TransitionController mTransitionController; private TaskFragmentOrganizer mOrganizer; private TaskFragmentOrganizerToken mOrganizerToken; private ITaskFragmentOrganizer mIOrganizer; @@ -107,9 +109,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private Task mTask; @Before - public void setup() { + public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); mWindowOrganizerController = mAtm.mWindowOrganizerController; + mTransitionController = mWindowOrganizerController.mTransitionController; mController = mWindowOrganizerController.mTaskFragmentOrganizerController; mOrganizer = new TaskFragmentOrganizer(Runnable::run); mOrganizerToken = mOrganizer.getOrganizerToken(); @@ -128,11 +131,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { spyOn(mController); spyOn(mOrganizer); spyOn(mTaskFragment); + spyOn(mWindowOrganizerController); + spyOn(mTransitionController); doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer(); doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo(); doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl(); doReturn(mFragmentToken).when(mTaskFragment).getFragmentToken(); doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); + + // To prevent it from calling the real server. + doNothing().when(mOrganizer).onTransactionHandled(any(), any()); } @Test @@ -866,7 +874,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertFalse(parentTask.shouldBeVisible(null)); // Verify the info changed callback still occurred despite the task being invisible - reset(mOrganizer); + clearInvocations(mOrganizer); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); @@ -899,7 +907,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); // Verify the info changed callback is not called when the task is invisible - reset(mOrganizer); + clearInvocations(mOrganizer); doReturn(false).when(task).shouldBeVisible(any()); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); @@ -1092,6 +1100,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .that(mTaskFragment.getBounds()).isEqualTo(task.getBounds()); } + @Test + public void testOnTransactionReady_invokeOnTransactionHandled() { + mController.registerOrganizer(mIOrganizer); + final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); + mOrganizer.onTransactionReady(transaction); + + // Organizer should always trigger #onTransactionHandled when receives #onTransactionReady + verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mOrganizer, never()).applyTransaction(any()); + } + + @Test + public void testDispatchTransaction_deferTransitionReady() { + mController.registerOrganizer(mIOrganizer); + setupMockParent(mTaskFragment, mTask); + final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class); + final ArgumentCaptor<WindowContainerTransaction> wctCaptor = + ArgumentCaptor.forClass(WindowContainerTransaction.class); + doReturn(true).when(mTransitionController).isCollecting(); + + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + // Defer transition when send TaskFragment transaction during transition collection. + verify(mTransitionController).deferTransitionReady(); + verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture()); + + mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue()); + + // Apply the organizer change and continue transition. + verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue()); + verify(mTransitionController).continueTransitionReady(); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction} to apply the transaction, diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 13da1543cfb8..d2cb7ba5d311 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -310,11 +310,11 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void startKeyguardExitAnimation(long startTime) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { } @Override - public int applyKeyguardOcclusionChange(boolean notify) { + public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) { return 0; } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index be7fb7315955..8df3548465bd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -21,7 +21,6 @@ import android.platform.test.annotations.Presubmit import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.common.ComponentMatcher import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.Assume import org.junit.Test 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 472a0fa376bf..eddb55346af8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -19,7 +19,7 @@ package com.android.server.wm.flicker import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.IComponentMatcher /** @@ -28,7 +28,7 @@ import com.android.server.wm.traces.common.IComponentMatcher */ fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() { assertWm { - this.isAboveAppWindowVisible(ComponentMatcher.STATUS_BAR) + this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) } } @@ -38,7 +38,7 @@ fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() { */ fun FlickerTestParameter.navBarWindowIsAlwaysVisible() { assertWm { - this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR) + this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } } @@ -48,10 +48,10 @@ fun FlickerTestParameter.navBarWindowIsAlwaysVisible() { */ fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() { assertWmStart { - this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR) + this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } assertWmEnd { - this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR) + this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } } @@ -61,7 +61,7 @@ fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() { */ fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() { assertWm { - this.isAboveAppWindowVisible(ComponentMatcher.TASK_BAR) + this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) } } @@ -71,7 +71,7 @@ fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() { */ fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() { assertWmEnd { - this.isAboveAppWindowVisible(ComponentMatcher.TASK_BAR) + this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) } } @@ -114,10 +114,10 @@ fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) { */ fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() { assertLayersStart { - this.isVisible(ComponentMatcher.NAV_BAR) + this.isVisible(ComponentNameMatcher.NAV_BAR) } assertLayersEnd { - this.isVisible(ComponentMatcher.NAV_BAR) + this.isVisible(ComponentNameMatcher.NAV_BAR) } } @@ -136,7 +136,7 @@ fun FlickerTestParameter.taskBarLayerIsVisibleAtStartAndEnd() { */ fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() { assertLayersStart { - this.isVisible(ComponentMatcher.TASK_BAR) + this.isVisible(ComponentNameMatcher.TASK_BAR) } } @@ -146,7 +146,7 @@ fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() { */ fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() { assertLayersEnd { - this.isVisible(ComponentMatcher.TASK_BAR) + this.isVisible(ComponentNameMatcher.TASK_BAR) } } @@ -156,10 +156,10 @@ fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() { */ fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() { assertLayersStart { - this.isVisible(ComponentMatcher.STATUS_BAR) + this.isVisible(ComponentNameMatcher.STATUS_BAR) } assertLayersEnd { - this.isVisible(ComponentMatcher.STATUS_BAR) + this.isVisible(ComponentNameMatcher.STATUS_BAR) } } @@ -171,7 +171,7 @@ fun FlickerTestParameter.navBarLayerPositionAtStart() { assertLayersStart { val display = this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!") - this.visibleRegion(ComponentMatcher.NAV_BAR) + this.visibleRegion(ComponentNameMatcher.NAV_BAR) .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation)) } } @@ -184,7 +184,7 @@ fun FlickerTestParameter.navBarLayerPositionAtEnd() { assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } ?: throw RuntimeException("There is no display!") - this.visibleRegion(ComponentMatcher.NAV_BAR) + this.visibleRegion(ComponentNameMatcher.NAV_BAR) .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation)) } } @@ -206,7 +206,7 @@ fun FlickerTestParameter.statusBarLayerPositionAtStart() { assertLayersStart { val display = this.entry.displays.minByOrNull { it.id } ?: throw RuntimeException("There is no display!") - this.visibleRegion(ComponentMatcher.STATUS_BAR) + this.visibleRegion(ComponentNameMatcher.STATUS_BAR) .coversExactly(WindowUtils.getStatusBarPosition(display)) } } @@ -219,7 +219,7 @@ fun FlickerTestParameter.statusBarLayerPositionAtEnd() { assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } ?: throw RuntimeException("There is no display!") - this.visibleRegion(ComponentMatcher.STATUS_BAR) + this.visibleRegion(ComponentNameMatcher.STATUS_BAR) .coversExactly(WindowUtils.getStatusBarPosition(display)) } } @@ -244,7 +244,7 @@ fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp( invoke("snapshotStartingWindowLayerCoversExactlyOnApp") { val snapshotLayers = it.subjects.filter { subject -> subject.name.contains( - ComponentMatcher.SNAPSHOT.toLayerName()) && subject.isVisible + ComponentNameMatcher.SNAPSHOT.toLayerName()) && subject.isVisible } // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation. if (snapshotLayers.isNotEmpty()) { @@ -291,10 +291,10 @@ fun FlickerTestParameter.replacesLayer( val assertion = this.isVisible(originalLayer) if (ignoreEntriesWithRotationLayer) { - assertion.then().isVisible(ComponentMatcher.ROTATION, isOptional = true) + assertion.then().isVisible(ComponentNameMatcher.ROTATION, isOptional = true) } if (ignoreSnapshot) { - assertion.then().isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + assertion.then().isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) } if (ignoreSplashscreen) { assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index 7ff093474399..cb197cdf04d3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -24,7 +24,7 @@ import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.traces.common.ComponentMatcher.Companion.LAUNCHER +import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER import org.junit.Test /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index af3a8c502008..b8fe9f9df882 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -26,7 +26,7 @@ import androidx.window.extensions.WindowExtensions import androidx.window.extensions.WindowExtensionsProvider import androidx.window.extensions.embedding.ActivityEmbeddingComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.windowmanager.WindowManagerState.Companion.STATE_RESUMED import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper @@ -35,7 +35,7 @@ import org.junit.Assume.assumeNotNull class ActivityEmbeddingAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME, - component: IComponentMatcher = MAIN_ACTIVITY_COMPONENT, + component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT, launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt index bd0ae4d47383..34f9ce43b4bc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt @@ -21,12 +21,11 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.provider.MediaStore -import com.android.server.wm.traces.common.ComponentMatcher -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher class CameraAppHelper @JvmOverloads constructor( - instrumentation: Instrumentation, - private val pkgManager: PackageManager = instrumentation.context.packageManager + instrumentation: Instrumentation, + pkgManager: PackageManager = instrumentation.context.packageManager ) : StandardAppHelper(instrumentation, getCameraLauncherName(pkgManager), getCameraComponent(pkgManager)){ companion object{ @@ -40,9 +39,9 @@ class CameraAppHelper @JvmOverloads constructor( ?: error("unable to resolve camera activity") } - private fun getCameraComponent(pkgManager: PackageManager): IComponentMatcher { + private fun getCameraComponent(pkgManager: PackageManager): ComponentNameMatcher { val resolveInfo = getResolveInfo(pkgManager) - return ComponentMatcher(resolveInfo.activityInfo.packageName, + return ComponentNameMatcher(resolveInfo.activityInfo.packageName, className = resolveInfo.activityInfo.name) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt index 28858d4843a1..b696fc30c19f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt @@ -20,13 +20,13 @@ import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class FixedOrientationAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index f536a151d8fd..e01cceb5d636 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -23,7 +23,7 @@ import android.view.WindowInsets.Type.statusBars import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import java.util.regex.Pattern @@ -33,7 +33,7 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( private val rotation: Int, private val imePackageName: String = IME_PACKAGE, launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { override fun openIME(wmHelper: WindowManagerStateHelper) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index db64c4769715..b672b1bc0da5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -22,14 +22,14 @@ import android.support.test.launcherhelper.LauncherStrategyFactory import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper open class ImeAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt index f74054ea4459..df47e9dfacd9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt @@ -20,16 +20,14 @@ import android.app.Instrumentation import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class ImeEditorPopupDialogAppHelper @JvmOverloads constructor( instr: Instrumentation, - private val rotation: Int, - private val imePackageName: String = IME_PACKAGE, launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { override fun openIME(wmHelper: WindowManagerStateHelper) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt index 7f8b5633391b..d3945c1749e3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt @@ -20,13 +20,13 @@ import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class ImeStateInitializeHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt index 149576e4ec1d..9fb574cf1c04 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt @@ -23,14 +23,14 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class NewTasksAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt index a9769d01caff..a1dbeeaef5cc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt @@ -20,13 +20,13 @@ import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class NonResizeableAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt index 50d036c4450f..b031a4523ab0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt @@ -22,14 +22,14 @@ import android.support.test.launcherhelper.LauncherStrategyFactory import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class NotificationAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt index 459ca5b3208f..6d466d72d7dd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt @@ -20,13 +20,13 @@ import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class SeamlessRotationAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt index 4952dbaeb551..804ab3864591 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt @@ -20,13 +20,13 @@ import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class ShowWhenLockedAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt index 6bddcac21a4d..5da273a7f1dc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt @@ -20,13 +20,13 @@ import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class SimpleAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index a17344f3879b..060e9af4a51c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -23,14 +23,14 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class TwoActivitiesAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME, - component: IComponentMatcher = + component: ComponentNameMatcher = ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index 6b8fde21e53d..f6f3f58f29cb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -83,7 +83,7 @@ class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest @Test fun imeLayerVisibleStart() { testSpec.assertLayersStart { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) } } @@ -91,7 +91,7 @@ class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest @Test fun imeLayerInvisibleEnd() { testSpec.assertLayersEnd { - this.isInvisible(ComponentMatcher.IME) + this.isInvisible(ComponentNameMatcher.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index a92ecb953a01..52f561ec4497 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -92,7 +92,7 @@ class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTes @Test fun imeLayerVisibleStart() { testSpec.assertLayersStart { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) } } @@ -100,7 +100,7 @@ class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTes @Test fun imeLayerInvisibleEnd() { testSpec.assertLayersEnd { - this.isInvisible(ComponentMatcher.IME) + this.isInvisible(ComponentNameMatcher.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt index 0e4d1dd009e2..c6e25d3de4cd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt @@ -28,7 +28,7 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { - private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation, testSpec.startRotation) + private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation) /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit = { @@ -102,12 +102,12 @@ class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(t @Test fun imeLayerAndImeSnapshotVisibleOnScreen() { testSpec.assertLayers { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) .then() - .isVisible(ComponentMatcher.IME_SNAPSHOT) + .isVisible(ComponentNameMatcher.IME_SNAPSHOT) .then() - .isInvisible(ComponentMatcher.IME_SNAPSHOT, isOptional = true) - .isInvisible(ComponentMatcher.IME) + .isInvisible(ComponentNameMatcher.IME_SNAPSHOT, isOptional = true) + .isInvisible(ComponentNameMatcher.IME) } } @@ -118,7 +118,7 @@ class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(t this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") { val imeSnapshotLayers = it.subjects.filter { subject -> subject.name.contains( - ComponentMatcher.IME_SNAPSHOT.toLayerName() + ComponentNameMatcher.IME_SNAPSHOT.toLayerName() ) && subject.isVisible } if (imeSnapshotLayers.isNotEmpty()) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 452aa63e2bf9..23bd2200397a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -73,9 +73,9 @@ class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpe override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( - ComponentMatcher.IME, - ComponentMatcher.SPLASH_SCREEN, - ComponentMatcher.SNAPSHOT)) + ComponentNameMatcher.IME, + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT)) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 856df260c2be..8ce184072d32 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -78,9 +78,9 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry( listOf( - ComponentMatcher.IME, - ComponentMatcher.SPLASH_SCREEN, - ComponentMatcher.SNAPSHOT + ComponentNameMatcher.IME, + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT ) ) } @@ -93,8 +93,8 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( listOf( - ComponentMatcher.IME, - ComponentMatcher.SPLASH_SCREEN + ComponentNameMatcher.IME, + ComponentNameMatcher.SPLASH_SCREEN ) ) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index 19cab3c20dae..9c99d966e345 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -18,52 +18,52 @@ package com.android.server.wm.flicker.ime import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher fun FlickerTestParameter.imeLayerBecomesVisible() { assertLayers { - this.isInvisible(ComponentMatcher.IME) + this.isInvisible(ComponentNameMatcher.IME) .then() - .isVisible(ComponentMatcher.IME) + .isVisible(ComponentNameMatcher.IME) } } fun FlickerTestParameter.imeLayerBecomesInvisible() { assertLayers { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) .then() - .isInvisible(ComponentMatcher.IME) + .isInvisible(ComponentNameMatcher.IME) } } fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertWm { - this.isNonAppWindowVisible(ComponentMatcher.IME) + this.isNonAppWindowVisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowInvisible(ComponentMatcher.IME) + .isNonAppWindowInvisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowVisible(ComponentMatcher.IME) + .isNonAppWindowVisible(ComponentNameMatcher.IME) } } else { assertWm { - this.isNonAppWindowVisible(ComponentMatcher.IME) + this.isNonAppWindowVisible(ComponentNameMatcher.IME) } } } fun FlickerTestParameter.imeWindowBecomesVisible() { assertWm { - this.isNonAppWindowInvisible(ComponentMatcher.IME) + this.isNonAppWindowInvisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowVisible(ComponentMatcher.IME) + .isNonAppWindowVisible(ComponentNameMatcher.IME) } } fun FlickerTestParameter.imeWindowBecomesInvisible() { assertWm { - this.isNonAppWindowVisible(ComponentMatcher.IME) + this.isNonAppWindowVisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowInvisible(ComponentMatcher.IME) + .isNonAppWindowInvisible(ComponentNameMatcher.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt index 4569a5b4910e..a04a50f02c26 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt @@ -30,7 +30,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.FixMethodOrder @@ -101,7 +101,7 @@ class LaunchAppShowImeAndDialogThemeAppTest( @Test fun imeLayerExistsEnd() { testSpec.assertLayersEnd { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) } } @@ -112,7 +112,7 @@ class LaunchAppShowImeAndDialogThemeAppTest( @Test fun imeSnapshotNotVisible() { testSpec.assertLayers { - this.isInvisible(ComponentMatcher.IME_SNAPSHOT) + this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt index 977719c5f670..04e4bc94de9c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -28,7 +28,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -114,7 +114,7 @@ class LaunchAppShowImeOnStartTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun imeLayerNotExistsStart() { testSpec.assertLayersStart { - this.isInvisible(ComponentMatcher.IME) + this.isInvisible(ComponentNameMatcher.IME) } } @@ -125,7 +125,7 @@ class LaunchAppShowImeOnStartTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun imeLayerExistsEnd() { testSpec.assertLayersEnd { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt index 16cf22abecc2..9475734ef3f5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt @@ -31,7 +31,7 @@ import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.WindowManagerConditionsFactory import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.Assume @@ -138,10 +138,10 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes Assume.assumeTrue(testSpec.isGesturalNavigation) Assume.assumeTrue(isShellTransitionsEnabled) testSpec.assertLayersStart { - this.isVisible(ComponentMatcher.NAV_BAR) + this.isVisible(ComponentNameMatcher.NAV_BAR) } testSpec.assertLayersEnd { - this.isInvisible(ComponentMatcher.NAV_BAR) + this.isInvisible(ComponentNameMatcher.NAV_BAR) } } @@ -156,10 +156,10 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes Assume.assumeTrue(testSpec.isGesturalNavigation) Assume.assumeFalse(testSpec.isTablet) testSpec.assertLayersStart { - this.isVisible(ComponentMatcher.STATUS_BAR) + this.isVisible(ComponentNameMatcher.STATUS_BAR) } testSpec.assertLayersEnd { - this.isInvisible(ComponentMatcher.STATUS_BAR) + this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } @@ -215,10 +215,10 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes Assume.assumeFalse(testSpec.isTablet) Assume.assumeTrue(isShellTransitionsEnabled) testSpec.assertLayersStart { - this.isVisible(ComponentMatcher.STATUS_BAR) + this.isVisible(ComponentNameMatcher.STATUS_BAR) } testSpec.assertLayersEnd { - this.isInvisible(ComponentMatcher.STATUS_BAR) + this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } @@ -235,7 +235,7 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun imeLayerIsVisibleAndAssociatedWithAppWidow() { testSpec.assertLayersStart { - isVisible(ComponentMatcher.IME).visibleRegion(ComponentMatcher.IME) + isVisible(ComponentNameMatcher.IME).visibleRegion(ComponentNameMatcher.IME) .coversAtMost( isVisible(imeTestApp) .visibleRegion(imeTestApp).region @@ -243,10 +243,10 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes } testSpec.assertLayers { this.invoke("imeLayerIsVisibleAndAlignAppWidow") { - val imeVisibleRegion = it.visibleRegion(ComponentMatcher.IME) + val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME) val appVisibleRegion = it.visibleRegion(imeTestApp) if (imeVisibleRegion.region.isNotEmpty) { - it.isVisible(ComponentMatcher.IME) + it.isVisible(ComponentNameMatcher.IME) imeVisibleRegion.coversAtMost(appVisibleRegion.region) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 2207fe571e9f..2e22e6224813 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -29,7 +29,7 @@ import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -84,11 +84,11 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp override fun visibleLayersShownMoreThanOneConsecutiveEntry() { // depends on how much of the animation transactions are sent to SF at once // sometimes this layer appears for 2-3 frames, sometimes for only 1 - val recentTaskComponent = ComponentMatcher("", "RecentTaskScreenshotSurface") + val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface") testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(ComponentMatcher.SPLASH_SCREEN, - ComponentMatcher.SNAPSHOT, recentTaskComponent) + listOf(ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, recentTaskComponent) ) } } @@ -97,11 +97,11 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Presubmit @Test override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - val component = ComponentMatcher("", "RecentTaskScreenshotSurface") + val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface") testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry( - ignoreWindows = listOf(ComponentMatcher.SPLASH_SCREEN, - ComponentMatcher.SNAPSHOT, + ignoreWindows = listOf(ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, component) ) } @@ -111,9 +111,9 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Test fun launcherWindowBecomesInvisible() { testSpec.assertWm { - this.isAppWindowVisible(ComponentMatcher.LAUNCHER) + this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER) .then() - .isAppWindowInvisible(ComponentMatcher.LAUNCHER) + .isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) } } @@ -157,11 +157,11 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp fun imeLayerIsBecomesVisibleLegacy() { Assume.assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) .then() - .isInvisible(ComponentMatcher.IME) + .isInvisible(ComponentNameMatcher.IME) .then() - .isVisible(ComponentMatcher.IME) + .isVisible(ComponentNameMatcher.IME) } } @@ -170,7 +170,7 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp fun imeLayerBecomesVisibleShellTransit() { Assume.assumeTrue(isShellTransitionsEnabled) testSpec.assertLayers { - this.isVisible(ComponentMatcher.IME) + this.isVisible(ComponentNameMatcher.IME) } } @@ -178,9 +178,9 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Test fun appLayerReplacesLauncher() { testSpec.assertLayers { - this.isVisible(ComponentMatcher.LAUNCHER) + this.isVisible(ComponentNameMatcher.LAUNCHER) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(testApp) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt index be7b80e8db82..4f47ec439da8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt @@ -31,7 +31,7 @@ import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -177,20 +177,20 @@ open class SwitchImeWindowsFromGestureNavTest( @Test open fun imeLayerIsVisibleWhenSwitchingToImeApp() { testSpec.assertLayersStart { - isVisible(ComponentMatcher.IME) + isVisible(ComponentNameMatcher.IME) } testSpec.assertLayersTag(TAG_IME_VISIBLE) { - isVisible(ComponentMatcher.IME) + isVisible(ComponentNameMatcher.IME) } testSpec.assertLayersEnd { - isVisible(ComponentMatcher.IME) + isVisible(ComponentNameMatcher.IME) } } @Test fun imeLayerIsInvisibleWhenSwitchingToTestApp() { testSpec.assertLayersTag(TAG_IME_INVISIBLE) { - isInvisible(ComponentMatcher.IME) + isInvisible(ComponentNameMatcher.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt index a8c0a0b55009..5ac1712adbf2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt @@ -24,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index 86b8e5fa1647..33c280ea78bb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent import org.junit.FixMethodOrder import org.junit.Test @@ -118,7 +118,7 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Test fun launcherWindowNotOnTop() { testSpec.assertWm { - this.isAppWindowNotOnTop(ComponentMatcher.LAUNCHER) + this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) } } @@ -128,7 +128,7 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Presubmit @Test fun launcherLayerNotVisible() { - testSpec.assertLayers { this.isInvisible(ComponentMatcher.LAUNCHER) } + testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) } } companion object { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt index 2d4d7986a67e..bece406d5a96 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt @@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Test /** @@ -46,7 +46,7 @@ abstract class OpenAppFromLauncherTransition( */ open fun appLayerReplacesLauncher() { testSpec.replacesLayer( - ComponentMatcher.LAUNCHER, testApp, + ComponentNameMatcher.LAUNCHER, testApp, ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true, ignoreSplashscreen = true ) @@ -61,12 +61,12 @@ abstract class OpenAppFromLauncherTransition( @Test open fun appWindowReplacesLauncherAsTopWindow() { testSpec.assertWm { - this.isAppWindowOnTop(ComponentMatcher.LAUNCHER) + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) .then() .isAppWindowOnTop( testApp - .or(ComponentMatcher.SNAPSHOT) - .or(ComponentMatcher.SPLASH_SCREEN) + .or(ComponentNameMatcher.SNAPSHOT) + .or(ComponentNameMatcher.SPLASH_SCREEN) ) } } 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 220e4caf5faa..bfc7b39f9d9f 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 @@ -26,7 +26,6 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.navBarLayerPositionAtEnd import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.ComponentMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore 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 9ed1bde86647..e517c2ad2c49 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 @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.navBarLayerPositionAtEnd import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -86,9 +86,9 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) : testSpec.assertWm { this.hasNoVisibleAppWindow() .then() - .isAppWindowOnTop(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() - .isAppWindowOnTop(ComponentMatcher.SPLASH_SCREEN, isOptional = true) + .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) .then() .isAppWindowOnTop(testApp) } 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 29730591c957..75311eaf5c66 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 @@ -26,7 +26,7 @@ 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.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -88,7 +88,7 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet testSpec.assertWm { this.hasNoVisibleAppWindow() .then() - .isAppWindowOnTop(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isAppWindowOnTop(showWhenLockedApp) } @@ -100,7 +100,7 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet testSpec.assertLayers { this.isInvisible(showWhenLockedApp) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(showWhenLockedApp) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt index 1d8b0a64c70f..ecc60b8d45c0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt @@ -22,7 +22,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.navBarLayerPositionAtEnd import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assume import org.junit.Ignore import org.junit.Test @@ -77,9 +77,9 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) : testSpec.assertWm { this.hasNoVisibleAppWindow() .then() - .isAppWindowOnTop(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() - .isAppWindowOnTop(ComponentMatcher.SPLASH_SCREEN, isOptional = true) + .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) .then() .isAppWindowOnTop(testApp) } @@ -152,7 +152,7 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) : @Test fun statusBarLayerIsVisibleAtEnd() { testSpec.assertLayersEnd { - this.isVisible(ComponentMatcher.STATUS_BAR) + this.isVisible(ComponentNameMatcher.STATUS_BAR) } } } 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 866e819bf5fc..78baddff582c 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 @@ -34,7 +34,6 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd -import com.android.server.wm.traces.common.ComponentMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 647607794fa9..53be7d43cce8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -29,7 +29,7 @@ import com.android.server.wm.flicker.annotation.FlickerServiceCompatible import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -76,9 +76,9 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : fun navBarLayerVisibilityChanges() { Assume.assumeFalse(testSpec.isTablet) testSpec.assertLayers { - this.isInvisible(ComponentMatcher.NAV_BAR) + this.isInvisible(ComponentNameMatcher.NAV_BAR) .then() - .isVisible(ComponentMatcher.NAV_BAR) + .isVisible(ComponentNameMatcher.NAV_BAR) } } @@ -102,9 +102,9 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : fun navBarWindowsVisibilityChanges() { Assume.assumeFalse(testSpec.isTablet) testSpec.assertWm { - this.isNonAppWindowInvisible(ComponentMatcher.NAV_BAR) + this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR) .then() - .isAboveAppWindowVisible(ComponentMatcher.NAV_BAR) + .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } } @@ -117,7 +117,7 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : fun taskBarLayerIsVisibleAtEnd() { Assume.assumeTrue(testSpec.isTablet) testSpec.assertLayersEnd { - this.isVisible(ComponentMatcher.TASK_BAR) + this.isVisible(ComponentNameMatcher.TASK_BAR) } } @@ -130,7 +130,7 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : @Test override fun statusBarLayerIsVisibleAtStartAndEnd() { testSpec.assertLayersEnd { - this.isVisible(ComponentMatcher.STATUS_BAR) + this.isVisible(ComponentNameMatcher.STATUS_BAR) } } @@ -180,7 +180,7 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : fun navBarLayerIsVisibleAtEnd() { Assume.assumeFalse(testSpec.isTablet) testSpec.assertLayersEnd { - this.isVisible(ComponentMatcher.NAV_BAR) + this.isVisible(ComponentNameMatcher.NAV_BAR) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index 5d2b56707de9..8658c03acf60 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -24,7 +24,7 @@ import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Test /** @@ -65,9 +65,9 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test .then() .isInvisible(testApp, isOptional = true) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() - .isVisible(ComponentMatcher.SPLASH_SCREEN, isOptional = true) + .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) .then() .isVisible(testApp) } @@ -77,9 +77,9 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test testSpec.assertLayers { this.isInvisible(testApp) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() - .isVisible(ComponentMatcher.SPLASH_SCREEN, isOptional = true) + .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) .then() .isVisible(testApp) } @@ -110,9 +110,9 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test testSpec.assertWm { this.isAppWindowInvisible(testApp) .then() - .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() - .isAppWindowVisible(ComponentMatcher.SPLASH_SCREEN, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) .then() .isAppWindowVisible(testApp) } @@ -130,8 +130,8 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test .then() .isAppWindowOnTop( testApp - .or(ComponentMatcher.SNAPSHOT) - .or(ComponentMatcher.SPLASH_SCREEN) + .or(ComponentNameMatcher.SNAPSHOT) + .or(ComponentNameMatcher.SPLASH_SCREEN) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index b482e5f3c3fb..fe5e74b87f93 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -30,9 +30,9 @@ import com.android.server.wm.flicker.helpers.NewTasksAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME -import com.android.server.wm.traces.common.ComponentMatcher -import com.android.server.wm.traces.common.ComponentMatcher.Companion.SPLASH_SCREEN -import com.android.server.wm.traces.common.ComponentMatcher.Companion.WALLPAPER_BBQ_WRAPPER +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN +import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER import com.android.server.wm.traces.common.IComponentMatcher import com.android.server.wm.traces.parser.toFlickerComponent import org.junit.FixMethodOrder @@ -117,7 +117,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { @Test fun launcherWindowIsNeverVisible() { testSpec.assertWm { - this.isAppWindowInvisible(ComponentMatcher.LAUNCHER) + this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) } } @@ -130,7 +130,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { @Test fun launcherLayerIsNeverVisible() { testSpec.assertLayers { - this.isInvisible(ComponentMatcher.LAUNCHER) + this.isInvisible(ComponentNameMatcher.LAUNCHER) } } @@ -140,7 +140,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { @Postsubmit @Test fun colorLayerIsVisibleDuringTransition() { - val bgColorLayer = ComponentMatcher("", "colorBackgroundLayer") + val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer") val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) testSpec.assertLayers { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index a9fb0f261465..181767b3448d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -30,7 +30,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.Rect import org.junit.Assume import org.junit.Before @@ -178,7 +178,7 @@ open class QuickSwitchBetweenTwoAppsBackTest( testSpec.assertWm { this.isAppWindowInvisible(testApp1) .then() - .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp1) } @@ -238,9 +238,9 @@ open class QuickSwitchBetweenTwoAppsBackTest( this.isAppWindowVisible(testApp2) .then() // TODO: Do we actually want to test this? Seems too implementation specific... - .isAppWindowVisible(ComponentMatcher.LAUNCHER, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) .then() - .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp1) } @@ -257,9 +257,9 @@ open class QuickSwitchBetweenTwoAppsBackTest( testSpec.assertLayers { this.isVisible(testApp2) .then() - .isVisible(ComponentMatcher.LAUNCHER, isOptional = true) + .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(testApp1) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt index 2607ee5bb0ef..461bae482299 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt @@ -24,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 3b602126399d..0f05622c81bc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -31,7 +31,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.Rect import org.junit.Assume import org.junit.Before @@ -112,7 +112,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest( @Test open fun startsWithApp1WindowsCoverFullScreen() { testSpec.assertWmStart { - this.visibleRegion(testApp1.or(ComponentMatcher.LETTERBOX)) + this.visibleRegion(testApp1.or(ComponentNameMatcher.LETTERBOX)) .coversExactly(startDisplayBounds) } } @@ -160,7 +160,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest( @Test open fun endsWithApp2LayersCoveringFullScreen() { testSpec.assertLayersEnd { - this.visibleRegion(testApp2.or(ComponentMatcher.LETTERBOX)) + this.visibleRegion(testApp2.or(ComponentNameMatcher.LETTERBOX)) .coversExactly(startDisplayBounds) } } @@ -187,7 +187,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest( testSpec.assertWm { this.isAppWindowInvisible(testApp2) .then() - .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp2) } @@ -246,9 +246,9 @@ open class QuickSwitchBetweenTwoAppsForwardTest( testSpec.assertWm { this.isAppWindowVisible(testApp1) .then() - .isAppWindowVisible(ComponentMatcher.LAUNCHER, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) .then() - .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp2) } @@ -265,9 +265,9 @@ open class QuickSwitchBetweenTwoAppsForwardTest( testSpec.assertLayers { this.isVisible(testApp1) .then() - .isVisible(ComponentMatcher.LAUNCHER, isOptional = true) + .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(testApp2) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt index 27ae12566e94..f644b97e03df 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt @@ -24,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index c79b55251c74..d1f356c830eb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -30,7 +30,7 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.Rect import org.junit.Assume import org.junit.FixMethodOrder @@ -152,7 +152,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun startsWithLauncherWindowsCoverFullScreen() { testSpec.assertWmStart { - this.visibleRegion(ComponentMatcher.LAUNCHER).coversExactly(startDisplayBounds) + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) } } @@ -164,7 +164,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun startsWithLauncherLayersCoverFullScreen() { testSpec.assertLayersStart { - this.visibleRegion(ComponentMatcher.LAUNCHER).coversExactly(startDisplayBounds) + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) } } @@ -175,7 +175,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun startsWithLauncherBeingOnTop() { testSpec.assertWmStart { - this.isAppWindowOnTop(ComponentMatcher.LAUNCHER) + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } } @@ -228,9 +228,9 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun launcherWindowBecomesAndStaysInvisible() { testSpec.assertWm { - this.isAppWindowOnTop(ComponentMatcher.LAUNCHER) + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) .then() - .isAppWindowNotOnTop(ComponentMatcher.LAUNCHER) + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) } } @@ -243,9 +243,9 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun launcherLayerBecomesAndStaysInvisible() { testSpec.assertLayers { - this.isVisible(ComponentMatcher.LAUNCHER) + this.isVisible(ComponentNameMatcher.LAUNCHER) .then() - .isInvisible(ComponentMatcher.LAUNCHER) + .isInvisible(ComponentNameMatcher.LAUNCHER) } } @@ -258,9 +258,9 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { testSpec.assertWm { - this.isAppWindowOnTop(ComponentMatcher.LAUNCHER) + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) .then() - .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp) } @@ -275,9 +275,9 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes @Test fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { testSpec.assertLayers { - this.isVisible(ComponentMatcher.LAUNCHER) + this.isVisible(ComponentNameMatcher.LAUNCHER) .then() - .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true) + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(testApp) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 5e80fabcd8a4..4be8963bf7b7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -25,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -106,10 +106,10 @@ class ChangeAppRotationTest( testSpec.assertLayers { this.isVisible(testApp) .then() - .isVisible(ComponentMatcher.ROTATION) + .isVisible(ComponentNameMatcher.ROTATION) .then() .isVisible(testApp) - .isInvisible(ComponentMatcher.ROTATION) + .isInvisible(ComponentNameMatcher.ROTATION) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 36a152117dcc..7e159d465b13 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -22,7 +22,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.Test /** @@ -55,9 +55,9 @@ abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(tes testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( ignoreLayers = listOf( - ComponentMatcher.SPLASH_SCREEN, - ComponentMatcher.SNAPSHOT, - ComponentMatcher("", "SecondaryHomeHandle") + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, + ComponentNameMatcher("", "SecondaryHomeHandle") ) ) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 1e3caa43a07c..0912812afef9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.ComponentMatcher +import com.android.server.wm.traces.common.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test @@ -186,7 +186,7 @@ open class SeamlessAppRotationTest( @Test fun statusBarWindowIsAlwaysInvisible() { testSpec.assertWm { - this.isAboveAppWindowInvisible(ComponentMatcher.STATUS_BAR) + this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) } } @@ -198,7 +198,7 @@ open class SeamlessAppRotationTest( @Test fun statusBarLayerIsAlwaysInvisible() { testSpec.assertLayers { - this.isInvisible(ComponentMatcher.STATUS_BAR) + this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 39ac24b29f35..b3165d320e9b 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1981,10 +1981,11 @@ class Action : public ManifestExtractor::Element { if (ElementCast<Activity>(parent_stack[1])) { // Detects the presence of a particular type of activity. Activity* activity = ElementCast<Activity>(parent_stack[1]); - auto map = std::map<std::string, std::string>({ - { "android.intent.action.MAIN" , "main" }, - { "android.intent.action.VIDEO_CAMERA" , "camera" }, - { "android.intent.action.STILL_IMAGE_CAMERA_SECURE" , "camera-secure" }, + static const auto map = std::map<std::string, std::string>({ + {"android.intent.action.MAIN", "main"}, + {"android.media.action.VIDEO_CAMERA", "camera"}, + {"android.media.action.STILL_IMAGE_CAMERA", "camera"}, + {"android.media.action.STILL_IMAGE_CAMERA_SECURE", "camera-secure"}, }); auto entry = map.find(action); @@ -2735,10 +2736,9 @@ bool ManifestExtractor::Extract(android::IDiagnostics* diag) { auto it = apk_->GetFileCollection()->Iterator(); while (it->HasNext()) { auto file_path = it->Next()->GetSource().path; - size_t pos = file_path.find("lib/"); - if (pos != std::string::npos) { - file_path = file_path.substr(pos + 4); - pos = file_path.find('/'); + if (file_path.starts_with("lib/")) { + file_path = file_path.substr(4); + size_t pos = file_path.find('/'); if (pos != std::string::npos) { file_path = file_path.substr(0, pos); } diff --git a/tools/aapt2/integration-tests/DumpTest/components.apk b/tools/aapt2/integration-tests/DumpTest/components.apk Binary files differindex deb55ea6fda3..a34ec83f3dae 100644 --- a/tools/aapt2/integration-tests/DumpTest/components.apk +++ b/tools/aapt2/integration-tests/DumpTest/components.apk diff --git a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt index bd7673616ddc..8e733a5db034 100644 --- a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt +++ b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt @@ -1677,7 +1677,7 @@ xml_files { attribute { namespace_uri: "http://schemas.android.com/apk/res/android" name: "name" - value: "android.intent.action.VIDEO_CAMERA" + value: "android.media.action.VIDEO_CAMERA" resource_id: 16842755 } } @@ -1691,7 +1691,7 @@ xml_files { attribute { namespace_uri: "http://schemas.android.com/apk/res/android" name: "name" - value: "android.intent.action.STILL_IMAGE_CAMERA_SECURE" + value: "android.media.action.STILL_IMAGE_CAMERA_SECURE" resource_id: 16842755 } } diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp index 260104145505..96618f413db1 100644 --- a/tools/lint/Android.bp +++ b/tools/lint/Android.bp @@ -51,3 +51,9 @@ java_test_host { unit_test: true, }, } + +python_binary_host { + name: "lint_fix", + main: "fix/lint_fix.py", + srcs: ["fix/lint_fix.py"], +} diff --git a/tools/lint/README.md b/tools/lint/README.md index c674d36431b7..99149c140c1c 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -44,6 +44,10 @@ m out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/a environment variable with the id of the lint. For example: `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html` +## How to apply automatic fixes suggested by lint + +See [lint_fix](fix/README.md) + ## Create or update a baseline Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md new file mode 100644 index 000000000000..367d0bcb1aa7 --- /dev/null +++ b/tools/lint/fix/README.md @@ -0,0 +1,46 @@ +# Refactoring the platform with lint +Inspiration: go/refactor-the-platform-with-lint\ +**Special Thanks: brufino@, azharaa@, for the prior work that made this all possible** + +## What is this? + +It's a python script that runs the framework linter, +and then copies modified files back into the source tree.\ +Why python, you ask? Because python is cool ¯\_(ツ)_/¯. + +## Why? + +Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag. +As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate +directory. This script runs the lint, unpacks those files, and copies them back into the tree. + +## How do I run it? +**WARNING: You probably want to commit/stash any changes to your working tree before doing this...** + +From this directory, run `python lint_fix.py -h`. +The script's help output explains things that are omitted here. + +Alternatively, there is a python binary target you can build to make this +available anywhere in your tree: +``` +m lint_fix +lint_fix -h +``` + +**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. + +Example: `lint_fix frameworks/base/services/core/services.core.unboosted UseEnforcePermissionAnnotation --dry-run` +```shell +( +export ANDROID_LINT_CHECK=UseEnforcePermissionAnnotation; +cd $ANDROID_BUILD_TOP; +source build/envsetup.sh; +rm out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html; +m out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html; +cd out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint; +unzip suggested-fixes.zip -d suggested-fixes; +cd suggested-fixes; +find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --; +rm -rf suggested-fixes +) +``` diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py new file mode 100644 index 000000000000..3ff8131489ff --- /dev/null +++ b/tools/lint/fix/lint_fix.py @@ -0,0 +1,76 @@ +import argparse +import os +import sys + +ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") +PATH_PREFIX = "out/soong/.intermediates" +PATH_SUFFIX = "android_common/lint" +FIX_DIR = "suggested-fixes" + +parser = argparse.ArgumentParser(description=""" +This is a python script that applies lint fixes to the platform: +1. Set up the environment, etc. +2. Build the lint and run it. +3. Unpack soong's intermediate zip containing source files modified by lint. +4. Copy the modified files back into the tree. + +**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` \ +so that the `ANDROID_BUILD_TOP` environment variable has been set. +Alternatively, set it manually in your shell. +""", formatter_class=argparse.RawTextHelpFormatter) + +parser.add_argument('build_path', metavar='build_path', type=str, + help='The build module to run ' + '(e.g. frameworks/base/framework-minus-apex or ' + 'frameworks/base/services/core/services.core.unboosted)') + +parser.add_argument('--check', metavar='check', type=str, + help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') + +parser.add_argument('--dry-run', dest='dry_run', action='store_true', + help='Just print the resulting shell script instead of running it.') + +parser.add_argument('--no-fix', dest='no_fix', action='store_true', + help='Just build and run the lint, do NOT apply the fixes.') + +args = parser.parse_args() + +path = f"{PATH_PREFIX}/{args.build_path}/{PATH_SUFFIX}" +target = f"{path}/lint-report.html" + +commands = [] + +if not args.dry_run: + commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"] + +if args.check: + commands += [f"export ANDROID_LINT_CHECK={args.check}"] + +commands += [ + "cd $ANDROID_BUILD_TOP", + "source build/envsetup.sh", + f"rm {target}", # remove the file first so soong doesn't think there is no work to do + f"m {target}", +] + +if not args.no_fix: + commands += [ + f"cd {path}", + f"unzip {FIX_DIR}.zip -d {FIX_DIR}", + f"cd {FIX_DIR}", + # Find all the java files in the fix directory, excluding the ./out subdirectory, + # and copy them back into the same path within the tree. + f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --", + f"rm -rf {FIX_DIR}" + ] + +if args.dry_run: + print("(\n" + ";\n".join(commands) + "\n)") + sys.exit(0) + +with_echo = [] +for c in commands: + with_echo.append(f'echo "{c}"') + with_echo.append(c) + +os.system("(\n" + ";\n".join(with_echo) + "\n)") |