Merge "Tapping dream media chip opens media instead of UMO if flag is set." into tm-qpr-dev
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 5ef6855..30eacf3 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
@@ -68,6 +68,11 @@
*/
private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>();
+ @GuardedBy("mLock")
+ private Boolean mLastReportedStatsdBatteryNotLow = null;
+ @GuardedBy("mLock")
+ private Boolean mLastReportedStatsdStablePower = null;
+
public BatteryController(JobSchedulerService service) {
super(service);
mPowerTracker = new PowerTracker();
@@ -173,6 +178,19 @@
Slog.d(TAG, "maybeReportNewChargingStateLocked: "
+ powerConnected + "/" + stablePower + "/" + batteryNotLow);
}
+
+ if (mLastReportedStatsdStablePower == null
+ || mLastReportedStatsdStablePower != stablePower) {
+ logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_CHARGING, stablePower);
+ mLastReportedStatsdStablePower = stablePower;
+ }
+ if (mLastReportedStatsdBatteryNotLow == null
+ || mLastReportedStatsdBatteryNotLow != stablePower) {
+ logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_BATTERY_NOT_LOW,
+ batteryNotLow);
+ mLastReportedStatsdBatteryNotLow = batteryNotLow;
+ }
+
final long nowElapsed = sElapsedRealtimeClock.millis();
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.valueAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index f6de109..abbe177 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -153,6 +153,8 @@
changed = true;
}
mDeviceIdleMode = enabled;
+ logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_DEVICE_NOT_DOZING,
+ !mDeviceIdleMode);
if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
mDeviceIdleUpdateFunctor.prepare();
if (enabled) {
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 a6fae2c..8311dc3 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
@@ -93,6 +93,8 @@
@Override
public void reportNewIdleState(boolean isIdle) {
synchronized (mLock) {
+ logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_IDLE, isIdle);
+
final long nowElapsed = sElapsedRealtimeClock.millis();
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/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 866dc41..0d85dfd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -151,13 +151,12 @@
*/
private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
| CONSTRAINT_DEADLINE
- | CONSTRAINT_IDLE
| CONSTRAINT_PREFETCH
| CONSTRAINT_TARE_WEALTH
| CONSTRAINT_TIMING_DELAY
| CONSTRAINT_WITHIN_QUOTA;
- // TODO(b/129954980)
+ // TODO(b/129954980): ensure this doesn't spam statsd, especially at boot
private static final boolean STATS_LOG_ENABLED = false;
// No override.
@@ -1864,7 +1863,7 @@
}
/** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */
- private int getProtoConstraint(int constraint) {
+ static int getProtoConstraint(int constraint) {
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
@@ -1882,8 +1881,12 @@
return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING;
case CONSTRAINT_IDLE:
return JobServerProtoEnums.CONSTRAINT_IDLE;
+ case CONSTRAINT_PREFETCH:
+ return JobServerProtoEnums.CONSTRAINT_PREFETCH;
case CONSTRAINT_STORAGE_NOT_LOW:
return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW;
+ case CONSTRAINT_TARE_WEALTH:
+ return JobServerProtoEnums.CONSTRAINT_TARE_WEALTH;
case CONSTRAINT_TIMING_DELAY:
return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY;
case CONSTRAINT_WITHIN_QUOTA:
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 2a2d602..8453e53 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -26,6 +26,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.StateChangedListener;
@@ -165,6 +166,15 @@
return mService.areComponentsInPlaceLocked(jobStatus);
}
+ protected void logDeviceWideConstraintStateToStatsd(int constraint, boolean satisfied) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.DEVICE_WIDE_JOB_CONSTRAINT_CHANGED,
+ JobStatus.getProtoConstraint(constraint),
+ satisfied
+ ? FrameworkStatsLog.DEVICE_WIDE_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED
+ : FrameworkStatsLog.DEVICE_WIDE_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED);
+ }
+
public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate);
public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f5ee467..93c0c4d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3406,6 +3406,7 @@
method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
method public int describeContents();
+ method @NonNull public android.window.WindowContainerTransaction finishActivity(@NonNull android.os.IBinder);
method @NonNull public android.window.WindowContainerTransaction removeTask(@NonNull android.window.WindowContainerToken);
method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 163d6ed..4324442 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
@@ -36,6 +37,7 @@
private static final String TAG = "DreamOverlayService";
private static final boolean DEBUG = false;
private boolean mShowComplications;
+ private ComponentName mDreamComponent;
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
@@ -56,6 +58,8 @@
public final IBinder onBind(@NonNull Intent intent) {
mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
DreamService.DEFAULT_SHOW_COMPLICATIONS);
+ mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT,
+ ComponentName.class);
return mDreamOverlay.asBinder();
}
@@ -84,4 +88,12 @@
public final boolean shouldShowComplications() {
return mShowComplications;
}
+
+ /**
+ * Returns the active dream component.
+ * @hide
+ */
+ public final ComponentName getDreamComponent() {
+ return mDreamComponent;
+ }
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 7515538..d066ee7 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -218,6 +218,12 @@
"android.service.dreams.SHOW_COMPLICATIONS";
/**
+ * Extra containing the component name for the active dream.
+ * @hide
+ */
+ public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT";
+
+ /**
* The default value for whether to show complications on the overlay.
* @hide
*/
@@ -271,6 +277,7 @@
overlayIntent.setComponent(overlayService);
overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
fetchShouldShowComplications(context, serviceInfo));
+ overlayIntent.putExtra(EXTRA_DREAM_COMPONENT, dreamService);
context.bindService(overlayIntent,
this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index d755d38..eac3bee 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -87,6 +87,14 @@
public static final int MAXIMUM_NUMBER_OF_INITIALIZATION_STATUS_CUSTOM_ERROR = 2;
/**
+ * Feature flag for Attention Service.
+ *
+ * TODO(b/247920386): Add TestApi annotation
+ * @hide
+ */
+ public static final boolean ENABLE_PROXIMITY_RESULT = false;
+
+ /**
* Indicates that the updated status is successful.
*/
public static final int INITIALIZATION_STATUS_SUCCESS = 0;
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index ffbdf08..cfad1af 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -706,6 +706,23 @@
}
/**
+ * Finishes the Activity.
+ * Comparing to directly calling {@link android.app.Activity#finish()}, calling this can make
+ * sure the finishing happens in the same transaction with other operations.
+ * @param activityToken activity to be finished.
+ */
+ @NonNull
+ public WindowContainerTransaction finishActivity(@NonNull IBinder activityToken) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY)
+ .setContainer(activityToken)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets/removes the always on top flag for this {@code windowContainer}. See
* {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
* Please note that this method is only intended to be used for a
@@ -1163,6 +1180,7 @@
public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
+ public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1484,6 +1502,8 @@
+ " alwaysOnTop=" + mAlwaysOnTop + "}";
case HIERARCHY_OP_TYPE_REMOVE_TASK:
return "{RemoveTask: task=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
+ return "{finishActivity: activity=" + mContainer + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
diff --git a/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl b/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl
new file mode 100644
index 0000000..b2236c9
--- /dev/null
+++ b/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+/**
+ * IPC interface for an application to receive callbacks from the log access dialog callback.
+ */
+oneway interface ILogAccessDialogCallback {
+ void approveAccessForClient(int uid, String packageName);
+ void declineAccessForClient(int uid, String packageName);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/core/java/com/android/internal/app/LogAccessDialogActivity.java
similarity index 71%
rename from services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
rename to core/java/com/android/internal/app/LogAccessDialogActivity.java
index 811e96c..4adb867 100644
--- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
+++ b/core/java/com/android/internal/app/LogAccessDialogActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.logcat;
+package com.android.internal.app;
import android.annotation.StyleRes;
import android.app.Activity;
@@ -27,7 +27,14 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.InflateException;
@@ -37,7 +44,6 @@
import android.widget.TextView;
import com.android.internal.R;
-import com.android.server.LocalServices;
/**
* Dialog responsible for obtaining user consent per-use log access
@@ -45,17 +51,19 @@
public class LogAccessDialogActivity extends Activity implements
View.OnClickListener {
private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
+ public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
+
private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
private static final int MSG_DISMISS_DIALOG = 0;
- private final LogcatManagerService.LogcatManagerServiceInternal mLogcatManagerInternal =
- LocalServices.getService(LogcatManagerService.LogcatManagerServiceInternal.class);
-
private String mPackageName;
private int mUid;
+ private ILogAccessDialogCallback mCallback;
private String mAlertTitle;
+ private String mAlertBody;
+ private String mAlertLearnMore;
private AlertDialog.Builder mAlertDialog;
private AlertDialog mAlert;
private View mAlertView;
@@ -81,6 +89,9 @@
return;
}
+ mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
+ mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
+
// create View
boolean isDarkTheme = (getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
@@ -118,6 +129,13 @@
return false;
}
+ mCallback = ILogAccessDialogCallback.Stub.asInterface(
+ intent.getExtras().getBinder(EXTRA_CALLBACK));
+ if (mCallback == null) {
+ Slog.e(TAG, "Missing callback");
+ return false;
+ }
+
mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
if (mPackageName == null || mPackageName.length() == 0) {
Slog.e(TAG, "Missing package name extra");
@@ -165,13 +183,22 @@
return titleString;
}
+ private Spannable styleFont(String text) {
+ Spannable s = (Spannable) Html.fromHtml(text);
+ for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
+ TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
+ s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
+ }
+ return s;
+ }
+
/**
* Returns the dialog view.
* If we cannot retrieve the package name, it returns null and we decline the full device log
* access
*/
private View createView(@StyleRes int themeId) {
- Context themedContext = new ContextThemeWrapper(getApplicationContext(), themeId);
+ Context themedContext = new ContextThemeWrapper(this, themeId);
final View view = LayoutInflater.from(themedContext).inflate(
R.layout.log_access_user_consent_dialog_permission, null /*root*/);
@@ -182,6 +209,19 @@
((TextView) view.findViewById(R.id.log_access_dialog_title))
.setText(mAlertTitle);
+ if (!TextUtils.isEmpty(mAlertLearnMore)) {
+ Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);
+
+ ((TextView) view.findViewById(R.id.log_access_dialog_body))
+ .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));
+
+ ((TextView) view.findViewById(R.id.log_access_dialog_body))
+ .setMovementMethod(LinkMovementMethod.getInstance());
+ } else {
+ ((TextView) view.findViewById(R.id.log_access_dialog_body))
+ .setText(mAlertBody);
+ }
+
Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
button_allow.setOnClickListener(this);
@@ -194,19 +234,27 @@
@Override
public void onClick(View view) {
- switch (view.getId()) {
- case R.id.log_access_dialog_allow_button:
- mLogcatManagerInternal.approveAccessForClient(mUid, mPackageName);
- finish();
- break;
- case R.id.log_access_dialog_deny_button:
- declineLogAccess();
- finish();
- break;
+ try {
+ switch (view.getId()) {
+ case R.id.log_access_dialog_allow_button:
+ mCallback.approveAccessForClient(mUid, mPackageName);
+ finish();
+ break;
+ case R.id.log_access_dialog_deny_button:
+ declineLogAccess();
+ finish();
+ break;
+ }
+ } catch (RemoteException e) {
+ finish();
}
}
private void declineLogAccess() {
- mLogcatManagerInternal.declineAccessForClient(mUid, mPackageName);
+ try {
+ mCallback.declineAccessForClient(mUid, mPackageName);
+ } catch (RemoteException e) {
+ finish();
+ }
}
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 87e8ac1..72b9cd2 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -473,7 +473,10 @@
}
}
mCurCombinedState = state;
- mStats.mUidStates.get(mUid).updateCombinedState(state, now);
+ final UidState uidState = mStats.mUidStates.get(mUid);
+ if (uidState != null) {
+ uidState.updateCombinedState(state, now);
+ }
}
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 72de78c..708713b 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -29,7 +29,9 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
@@ -219,6 +221,8 @@
public static final int CUJ_TASKBAR_EXPAND = 60;
public static final int CUJ_TASKBAR_COLLAPSE = 61;
public static final int CUJ_SHADE_CLEAR_ALL = 62;
+ public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
+ public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
private static final int NO_STATSD_LOGGING = -1;
@@ -290,6 +294,8 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
};
private static volatile InteractionJankMonitor sInstance;
@@ -372,7 +378,9 @@
CUJ_USER_DIALOG_OPEN,
CUJ_TASKBAR_EXPAND,
CUJ_TASKBAR_COLLAPSE,
- CUJ_SHADE_CLEAR_ALL
+ CUJ_SHADE_CLEAR_ALL,
+ CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+ CUJ_LOCKSCREEN_OCCLUSION
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -864,6 +872,10 @@
return "TASKBAR_COLLAPSE";
case CUJ_SHADE_CLEAR_ALL:
return "SHADE_CLEAR_ALL";
+ case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
+ return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
+ case CUJ_LOCKSCREEN_OCCLUSION:
+ return "LOCKSCREEN_OCCLUSION";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index d3f9e0a..8fcb6d5 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -29,6 +29,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS;
@@ -174,6 +175,12 @@
*/
public static final int ACTION_FOLD_TO_AOD = 18;
+ /**
+ * Time it takes to show the {@link android.service.voice.VoiceInteractionSession} system UI
+ * after a {@link android.hardware.soundtrigger3.ISoundTriggerHw} voice trigger.
+ */
+ public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -194,6 +201,7 @@
ACTION_LOAD_SHARE_SHEET,
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
+ ACTION_SHOW_VOICE_INTERACTION,
};
/** @hide */
@@ -217,6 +225,7 @@
ACTION_LOAD_SHARE_SHEET,
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
+ ACTION_SHOW_VOICE_INTERACTION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -243,6 +252,7 @@
UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
};
private static LatencyTracker sLatencyTracker;
@@ -340,6 +350,8 @@
return "ACTION_SHOW_SELECTION_TOOLBAR";
case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD:
return "ACTION_FOLD_TO_AOD";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
+ return "ACTION_SHOW_VOICE_INTERACTION";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b91bd18..5ae133b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6783,8 +6783,9 @@
android:exported="false">
</activity>
- <activity android:name="com.android.server.logcat.LogAccessDialogActivity"
+ <activity android:name="com.android.internal.app.LogAccessDialogActivity"
android:theme="@style/Theme.Translucent.NoTitleBar"
+ android:process=":ui"
android:excludeFromRecents="true"
android:exported="false">
</activity>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6ec98e8..5763345 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5755,10 +5755,21 @@
<string name="log_access_confirmation_deny">Don\u2019t allow</string>
<!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
- <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+ <string name="log_access_confirmation_body" product="default">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
\n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
</string>
+ <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+ <string name="log_access_confirmation_body" product="tv">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+ \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
+ </string>
+
+ <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+ <string name="log_access_confirmation_learn_more" product="default" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string>
+
+ <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+ <string name="log_access_confirmation_learn_more" product="tv" translatable="false"></string>
+
<!-- Privacy notice do not show [CHAR LIMIT=20] -->
<string name="log_access_do_not_show_again">Don\u2019t show again</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 546bb21..6e574bd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3925,8 +3925,10 @@
<java-symbol type="string" name="log_access_confirmation_deny" />
<java-symbol type="string" name="log_access_confirmation_title" />
<java-symbol type="string" name="log_access_confirmation_body" />
+ <java-symbol type="string" name="log_access_confirmation_learn_more" />
<java-symbol type="layout" name="log_access_user_consent_dialog_permission" />
<java-symbol type="id" name="log_access_dialog_title" />
+ <java-symbol type="id" name="log_access_dialog_body" />
<java-symbol type="id" name="log_access_dialog_allow_button" />
<java-symbol type="id" name="log_access_dialog_deny_button" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 626e0d9..18712ae 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -432,7 +432,7 @@
// In case we have requested to reparent the activity to another container (as
// pendingAppeared), we don't want to finish it with this container.
&& mController.getContainerWithActivity(activity) == this) {
- activity.finish();
+ wct.finishActivity(activity.getActivityToken());
}
}
@@ -457,7 +457,7 @@
|| controller.shouldRetainAssociatedActivity(this, activity)) {
continue;
}
- activity.finish();
+ wct.finishActivity(activity.getActivityToken());
}
mActivitiesToFinishOnExit.clear();
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 179696a..25d0347 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -207,7 +207,7 @@
verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
verify(mSplitController).removeContainer(tf);
- verify(mActivity, never()).finish();
+ verify(mTransaction, never()).finishActivity(any());
}
@Test
@@ -1004,9 +1004,9 @@
assertTrue(primaryContainer.isFinished());
assertTrue(secondaryContainer0.isFinished());
assertTrue(secondaryContainer1.isFinished());
- verify(mActivity).finish();
- verify(secondaryActivity0).finish();
- verify(secondaryActivity1).finish();
+ verify(mTransaction).finishActivity(mActivity.getActivityToken());
+ verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
+ verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
assertTrue(taskContainer.mContainers.isEmpty());
assertTrue(taskContainer.mSplitContainers.isEmpty());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 73428a2..35415d8 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -107,30 +107,29 @@
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
doReturn(container).when(mController).getContainerWithActivity(mActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
// Only remove the activity, but not clear the reference until appeared.
- container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+ container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
- verify(mActivity).finish();
+ verify(mTransaction).finishActivity(mActivity.getActivityToken());
verify(mPresenter, never()).deleteTaskFragment(any(), any());
verify(mController, never()).removeContainer(any());
// Calling twice should not finish activity again.
- clearInvocations(mActivity);
- container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+ clearInvocations(mTransaction);
+ container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
- verify(mActivity, never()).finish();
+ verify(mTransaction, never()).finishActivity(any());
verify(mPresenter, never()).deleteTaskFragment(any(), any());
verify(mController, never()).removeContainer(any());
// Remove all references after the container has appeared in server.
doReturn(new ArrayList<>()).when(mInfo).getActivities();
container.setInfo(mTransaction, mInfo);
- container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+ container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
- verify(mActivity, never()).finish();
- verify(mPresenter).deleteTaskFragment(wct, container.getTaskFragmentToken());
+ verify(mTransaction, never()).finishActivity(any());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container.getTaskFragmentToken());
verify(mController).removeContainer(container);
}
@@ -150,7 +149,7 @@
// The activity is requested to be reparented, so don't finish it.
container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
- verify(mActivity, never()).finish();
+ verify(mTransaction, never()).finishActivity(any());
verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
verify(mController).removeContainer(container0);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index ff4b2ed..f879994 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,6 +24,7 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.TaskInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
import android.util.Slog;
@@ -327,6 +328,28 @@
return recentTasks;
}
+ /**
+ * Find the background task that match the given component.
+ */
+ @Nullable
+ public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) {
+ if (componentName == null) {
+ return null;
+ }
+ List<ActivityManager.RecentTaskInfo> tasks = getRawRecentTasks(Integer.MAX_VALUE,
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
+ for (int i = 0; i < tasks.size(); i++) {
+ final ActivityManager.RecentTaskInfo task = tasks.get(i);
+ if (task.isVisible) {
+ continue;
+ }
+ if (componentName.equals(task.baseIntent.getComponent())) {
+ return task;
+ }
+ }
+ return null;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 991f136..07a6895 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -385,6 +385,9 @@
}
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictInvisibleChildTasks(evictWct);
+ mSyncQueue.queue(evictWct);
}
};
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
@@ -472,8 +475,16 @@
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split.
+ // split and there is no reusable background task.
if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
+ ? mRecentTasksOptional.get().findTaskInBackground(
+ intent.getIntent().getComponent())
+ : null;
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ return;
+ }
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 05fbc7a..23ee505 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"presubmit": [
{
+ "name": "mediaroutertest"
+ },
+ {
"name": "CtsCameraTestCases",
"options" : [
{
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index d8995b4..891ab45 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -48,6 +48,7 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -118,6 +119,7 @@
private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
+ private final AtomicBoolean mIsScanning = new AtomicBoolean(/* initialValue= */ false);
final Handler mHandler;
@@ -234,7 +236,9 @@
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void startScan() {
if (isSystemRouter()) {
- sManager.startScan();
+ if (!mIsScanning.getAndSet(true)) {
+ sManager.registerScanRequest();
+ }
}
}
@@ -260,7 +264,9 @@
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void stopScan() {
if (isSystemRouter()) {
- sManager.stopScan();
+ if (mIsScanning.getAndSet(false)) {
+ sManager.unregisterScanRequest();
+ }
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 071667a..d79740c 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -79,9 +79,11 @@
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;
final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
@@ -119,7 +121,12 @@
.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();
+ }
}
/**
@@ -155,48 +162,47 @@
}
/**
- * Starts scanning remote routes.
- * <p>
- * Route discovery can happen even when the {@link #startScan()} is not called.
- * This is because the scanning could be started before by other apps.
- * Therefore, calling this method after calling {@link #stopScan()} does not necessarily mean
- * that the routes found before are removed and added again.
- * <p>
- * Use {@link Callback} to get the route related events.
- * <p>
- * @see #stopScan()
+ * Registers a request to scan for remote routes.
+ *
+ * <p>Increases the count of active scanning requests. When the count transitions from zero to
+ * one, sends a request to the system server to start scanning.
+ *
+ * <p>Clients must {@link #unregisterScanRequest() unregister their scan requests} when scanning
+ * is no longer needed, to avoid unnecessary resource usage.
*/
- public void startScan() {
- Client client = getOrCreateClient();
- if (client != null) {
+ public void registerScanRequest() {
+ if (mScanRequestCount.getAndIncrement() == 0) {
try {
- mMediaRouterService.startScan(client);
+ mMediaRouterService.startScan(mClient);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to get sessions. Service probably died.", ex);
+ throw ex.rethrowFromSystemServer();
}
}
}
/**
- * Stops scanning remote routes to reduce resource consumption.
- * <p>
- * Route discovery can be continued even after this method is called.
- * This is because the scanning is only turned off when all the apps stop scanning.
- * Therefore, calling this method does not necessarily mean the routes are removed.
- * Also, for the same reason it does not mean that {@link Callback#onRoutesAdded(List)}
- * is not called afterwards.
- * <p>
- * Use {@link Callback} to get the route related events.
+ * Unregisters a scan request made by {@link #registerScanRequest()}.
*
- * @see #startScan()
+ * <p>Decreases the count of active scanning requests. When the count transitions from one to
+ * zero, sends a request to the system server to stop scanning.
+ *
+ * @throws IllegalStateException If called while there are no active scan requests.
*/
- public void stopScan() {
- Client client = getOrCreateClient();
- if (client != null) {
+ public void unregisterScanRequest() {
+ if (mScanRequestCount.updateAndGet(
+ count -> {
+ if (count == 0) {
+ throw new IllegalStateException(
+ "No active scan requests to unregister.");
+ } else {
+ return --count;
+ }
+ })
+ == 0) {
try {
- mMediaRouterService.stopScan(client);
+ mMediaRouterService.stopScan(mClient);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to get sessions. Service probably died.", ex);
+ throw ex.rethrowFromSystemServer();
}
}
}
@@ -358,12 +364,10 @@
@Nullable
public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) {
try {
- return mMediaRouterService.getSystemSessionInfoForPackage(
- getOrCreateClient(), packageName);
+ return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to get current system session info", ex);
+ throw ex.rethrowFromSystemServer();
}
- return null;
}
/**
@@ -424,15 +428,11 @@
*/
@NonNull
public List<RoutingSessionInfo> getRemoteSessions() {
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- return mMediaRouterService.getRemoteSessions(client);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to get sessions. Service probably died.", ex);
- }
+ try {
+ return mMediaRouterService.getRemoteSessions(mClient);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return Collections.emptyList();
}
/**
@@ -515,14 +515,11 @@
return;
}
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- int requestId = mNextRequestId.getAndIncrement();
- mMediaRouterService.setRouteVolumeWithManager(client, requestId, route, volume);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to set route volume.", ex);
- }
+ try {
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.setRouteVolumeWithManager(mClient, requestId, route, volume);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -544,15 +541,12 @@
return;
}
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- int requestId = mNextRequestId.getAndIncrement();
- mMediaRouterService.setSessionVolumeWithManager(
- client, requestId, sessionInfo.getId(), volume);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to set session volume.", ex);
- }
+ try {
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.setSessionVolumeWithManager(
+ mClient, requestId, sessionInfo.getId(), volume);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -809,15 +803,12 @@
return;
}
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- int requestId = mNextRequestId.getAndIncrement();
- mMediaRouterService.selectRouteWithManager(
- client, requestId, sessionInfo.getId(), route);
- } catch (RemoteException ex) {
- Log.e(TAG, "selectRoute: Failed to send a request.", ex);
- }
+ try {
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.selectRouteWithManager(
+ mClient, requestId, sessionInfo.getId(), route);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -851,15 +842,12 @@
return;
}
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- int requestId = mNextRequestId.getAndIncrement();
- mMediaRouterService.deselectRouteWithManager(
- client, requestId, sessionInfo.getId(), route);
- } catch (RemoteException ex) {
- Log.e(TAG, "deselectRoute: Failed to send a request.", ex);
- }
+ try {
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.deselectRouteWithManager(
+ mClient, requestId, sessionInfo.getId(), route);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -876,15 +864,11 @@
public void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- int requestId = mNextRequestId.getAndIncrement();
- mMediaRouterService.releaseSessionWithManager(
- client, requestId, sessionInfo.getId());
- } catch (RemoteException ex) {
- Log.e(TAG, "releaseSession: Failed to send a request", ex);
- }
+ try {
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.releaseSessionWithManager(mClient, requestId, sessionInfo.getId());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -897,14 +881,11 @@
@NonNull MediaRoute2Info route) {
int requestId = createTransferRequest(session, route);
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- mMediaRouterService.transferToRouteWithManager(
- client, requestId, session.getId(), route);
- } catch (RemoteException ex) {
- Log.e(TAG, "transferToRoute: Failed to send a request.", ex);
- }
+ try {
+ mMediaRouterService.transferToRouteWithManager(
+ mClient, requestId, session.getId(), route);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -917,14 +898,11 @@
int requestId = createTransferRequest(oldSession, route);
- Client client = getOrCreateClient();
- if (client != null) {
- try {
- mMediaRouterService.requestCreateSessionWithManager(
- client, requestId, oldSession, route);
- } catch (RemoteException ex) {
- Log.e(TAG, "requestCreateSession: Failed to send a request", ex);
- }
+ try {
+ mMediaRouterService.requestCreateSessionWithManager(
+ mClient, requestId, oldSession, route);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -968,23 +946,6 @@
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) {
- Log.e(TAG, "Unable to register media router manager.", ex);
- }
- }
- return null;
- }
-
/**
* Interface for receiving events about media routing changes.
*/
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index ecb1d51..9868a14 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -255,21 +255,21 @@
jmethodID audioProfileConstructorMethodID =
env->GetMethodID(audioProfileClazz, "<init>", "(IIIII)V");
- jobjectArray videoCodecs = (jobjectArray)env->NewObjectArray(
- cp->getVideoCodecs().size(), videoProfileClazz, nullptr);
+ jobjectArray videoCodecs = nullptr;
{
- int i = 0;
+ auto isAdvancedCodec = [](const MediaProfiles::VideoCodec *vc) -> bool {
+ return ((vc->getBitDepth() != 8
+ || vc->getChromaSubsampling() != CHROMA_SUBSAMPLING_YUV_420
+ || vc->getHdrFormat() != HDR_FORMAT_NONE));
+ };
+ std::vector<jobject> codecVector;
for (const MediaProfiles::VideoCodec *vc : cp->getVideoCodecs()) {
+ if (isAdvancedCodec(vc) && !static_cast<bool>(advanced)) {
+ continue;
+ }
chroma_subsampling cs = vc->getChromaSubsampling();
int bitDepth = vc->getBitDepth();
hdr_format hdr = vc->getHdrFormat();
-
- bool isAdvanced =
- (bitDepth != 8 || cs != CHROMA_SUBSAMPLING_YUV_420 || hdr != HDR_FORMAT_NONE);
- if (static_cast<bool>(advanced) && !isAdvanced) {
- continue;
- }
-
jobject videoCodec = env->NewObject(videoProfileClazz,
videoProfileConstructorMethodID,
vc->getCodec(),
@@ -281,10 +281,17 @@
static_cast<int>(cs),
bitDepth,
static_cast<int>(hdr));
- env->SetObjectArrayElement(videoCodecs, i++, videoCodec);
+
+ codecVector.push_back(videoCodec);
+ }
+ videoCodecs = (jobjectArray)env->NewObjectArray(codecVector.size(),
+ videoProfileClazz, nullptr);
+
+ int i = 0;
+ for (jobject codecObj : codecVector) {
+ env->SetObjectArrayElement(videoCodecs, i++, codecObj);
}
}
-
jobjectArray audioCodecs;
if (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LIST_START
&& quality <= CAMCORDER_QUALITY_TIME_LAPSE_LIST_END) {
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index 2da6c98..4f9c6f1 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -25,7 +25,7 @@
"testng",
"truth-prebuilt",
],
-
+ test_suites: ["general-tests"],
platform_apis: true,
certificate: "platform",
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index b4aad9d..4086dec 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -39,6 +39,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import android.Manifest;
@@ -121,7 +122,7 @@
MediaRouter2ManagerTestActivity.startActivity(mContext);
mManager = MediaRouter2Manager.getInstance(mContext);
- mManager.startScan();
+ mManager.registerScanRequest();
mRouter2 = MediaRouter2.getInstance(mContext);
// If we need to support thread pool executors, change this to thread pool executor.
@@ -152,7 +153,7 @@
@After
public void tearDown() {
- mManager.stopScan();
+ mManager.unregisterScanRequest();
// order matters (callbacks should be cleared at the last)
releaseAllSessions();
@@ -818,6 +819,13 @@
assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
+ @Test
+ public void unregisterScanRequest_enforcesANonNegativeCount() {
+ mManager.unregisterScanRequest(); // One request was made in the test setup.
+ assertThrows(IllegalStateException.class, () -> mManager.unregisterScanRequest());
+ mManager.registerScanRequest(); // So that the cleanup doesn't fail.
+ }
+
/**
* Tests if getSelectableRoutes and getDeselectableRoutes filter routes based on
* selected routes
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index f448fbf..9cdd103 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -25,7 +25,7 @@
<string name="permission_apps_summary" msgid="798718816711515431">"Трансліруйце змесціва праграм з вашага тэлефона"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Сэрвісы для некалькіх прылад"</string>
- <string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на перадачу праграм плынню паміж вашымі прыладамі"</string>
+ <string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
<string name="title_computer" msgid="4693714143506569253">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index c781ea1..6d9ad9d 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -23,12 +23,12 @@
<string name="summary_watch" msgid="3002344206574997652">"Se necesita esta aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, calendario, registros de llamadas y dispositivos cercanos."</string>
<string name="permission_apps" msgid="6142133265286656158">"Aplicaciones"</string>
<string name="permission_apps_summary" msgid="798718816711515431">"Proyecta aplicaciones de tu teléfono"</string>
- <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acceda a esta información desde tu teléfono"</string>
+ <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acceda a esta información de tu teléfono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para emitir aplicaciones en otros dispositivos tuyos"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
- <string name="title_computer" msgid="4693714143506569253">"Permitir que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acceda a esta información desde tu teléfono"</string>
+ <string name="title_computer" msgid="4693714143506569253">"Permitir que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acceda a esta información de tu teléfono"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
<string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluida información como contactos, mensajes y fotos"</string>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index 3997deb..6f28275 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -25,13 +25,13 @@
<string name="permission_apps_summary" msgid="798718816711515431">"Streymdu forritum símans"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aðgang að þessum upplýsingum úr símanum þínum"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Þjónustur á milli tækja"</string>
- <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild fyrir straumspilun forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
+ <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild til straumspilunar forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
<string name="title_computer" msgid="4693714143506569253">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aðgang að þessum upplýsingum úr símanum þínum"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"Tilkynningar"</string>
- <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði skilaboð og myndir"</string>
+ <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string>
<string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="helper_title_computer" msgid="4671071173916176037">"Þjónusta Google Play"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 24a3094..3e7b023 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -25,14 +25,14 @@
<string name="permission_apps_summary" msgid="798718816711515431">"Телефондогу колдонмолорду алып ойнотуу"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Түзмөктөр аралык кызматтар"</string>
- <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду тышкы экранга чыгарууга уруксат сурап жатат"</string>
+ <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду өткөрүүгө уруксат сурап жатат"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
<string name="title_computer" msgid="4693714143506569253">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"Билдирмелер"</string>
<string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string>
- <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиа"</string>
+ <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="helper_title_computer" msgid="4671071173916176037">"Google Play кызматтары"</string>
<string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан телефондогу сүрөттөрдү, медиа файлдарды жана билдирмелерди колдонууга уруксат сурап жатат"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index fa7155e..a7d8c5e 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -25,7 +25,7 @@
<string name="permission_apps_summary" msgid="798718816711515431">"Streamovať aplikácie telefónu"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Povoľte aplikácii <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> prístup k týmto informáciám z vášho telefónu"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pre viacero zariadení"</string>
- <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
+ <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
<string name="title_computer" msgid="4693714143506569253">"Povoľte aplikácii <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> prístup k týmto informáciám z vášho telefónu"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 24465fc..966ff55 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -25,7 +25,7 @@
<string name="permission_apps_summary" msgid="798718816711515431">"Tiririsha programu za simu yako"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ifikie maelezo haya kutoka kwenye simu yako"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string>
- <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
+ <string name="helper_summary_app_streaming" msgid="5977509499890099">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
<string name="title_computer" msgid="4693714143506569253">"Ruhusu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ifikie maelezo haya kutoka kwenye simu yako"</string>
@@ -35,7 +35,7 @@
<string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="helper_title_computer" msgid="4671071173916176037">"Huduma za Google Play"</string>
- <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string>
+ <string name="helper_summary_computer" msgid="9050724687678157852">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index 7440079..cd96095 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -31,7 +31,7 @@
<string name="title_computer" msgid="4693714143506569253">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> యాప్ను అనుమతించండి"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్లు"</string>
- <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్లు, మెసేజ్లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్లను చదవగలరు"</string>
+ <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్లు, మెసేజ్లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్లను చదవగలదు"</string>
<string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="helper_title_computer" msgid="4671071173916176037">"Google Play సర్వీసులు"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index fea7475..5c796af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -237,14 +237,10 @@
* @return true if it supports advanced metadata, false otherwise.
*/
public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) {
- if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
- true)) {
- Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
+ if (!isAdvancedHeaderEnabled()) {
return false;
}
- // The metadata is for Android R
- if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
- Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
+ if (isUntetheredHeadset(bluetoothDevice)) {
return true;
}
// The metadata is for Android S
@@ -260,6 +256,47 @@
}
/**
+ * Check if the Bluetooth device is supports advanced metadata and an untethered headset
+ *
+ * @param bluetoothDevice the BluetoothDevice to get metadata
+ * @return true if it supports advanced metadata and an untethered headset, false otherwise.
+ */
+ public static boolean isAdvancedUntetheredDevice(@NonNull BluetoothDevice bluetoothDevice) {
+ if (!isAdvancedHeaderEnabled()) {
+ return false;
+ }
+ if (isUntetheredHeadset(bluetoothDevice)) {
+ return true;
+ }
+ // The metadata is for Android S
+ String deviceType = getStringMetaData(bluetoothDevice,
+ BluetoothDevice.METADATA_DEVICE_TYPE);
+ if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
+ Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isAdvancedHeaderEnabled() {
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
+ true)) {
+ Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isUntetheredHeadset(@NonNull BluetoothDevice bluetoothDevice) {
+ // The metadata is for Android R
+ if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Create an Icon pointing to a drawable.
*/
public static IconCompat createIconWithDrawable(Drawable drawable) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 1be9d76..3903404 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -59,14 +59,14 @@
@Override
public Drawable getIcon() {
- return BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice())
+ return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice())
? mContext.getDrawable(R.drawable.ic_earbuds_advanced)
: BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedDevice).first;
}
@Override
public Drawable getIconWithoutBackground() {
- return BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice())
+ return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice())
? mContext.getDrawable(R.drawable.ic_earbuds_advanced)
: BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedDevice).first;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 58c15eb..7ec0fcd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -96,14 +96,14 @@
public void startScan() {
mMediaDevices.clear();
mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
- mRouterManager.startScan();
+ mRouterManager.registerScanRequest();
refreshDevices();
}
@Override
public void stopScan() {
mRouterManager.unregisterCallback(mMediaRouterCallback);
- mRouterManager.stopScan();
+ mRouterManager.unregisterScanRequest();
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 1c0ea1a..ca14573 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -205,4 +205,45 @@
public void isAdvancedDetailsHeader_noMetadata_returnFalse() {
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(false);
}
+
+ @Test
+ public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
+ when(mBluetoothDevice.getMetadata(
+ BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ BOOL_METADATA.getBytes());
+
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+ }
+
+ @Test
+ public void isAdvancedUntetheredDevice_deviceTypeUntetheredHeadset_returnTrue() {
+ when(mBluetoothDevice.getMetadata(
+ BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+ BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+ }
+
+ @Test
+ public void isAdvancedUntetheredDevice_deviceTypeWatch_returnFalse() {
+ when(mBluetoothDevice.getMetadata(
+ BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+ BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isAdvancedUntetheredDevice_deviceTypeDefault_returnFalse() {
+ when(mBluetoothDevice.getMetadata(
+ BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+ BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
+
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f50dc74..df6f08d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -124,6 +124,7 @@
"dagger2",
"jsr330",
"lottie",
+ "LowLightDreamLib",
],
manifest: "AndroidManifest.xml",
@@ -227,6 +228,7 @@
"dagger2",
"jsr330",
"WindowManager-Shell",
+ "LowLightDreamLib",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 8182484..9f275af 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -813,7 +813,7 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -828,7 +828,7 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 0c57b934..8388b67 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -103,6 +103,7 @@
android:layout_width="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
+ android:visibility="invisible"
android:clipChildren="false"
android:clipToPadding="false" />
</LinearLayout>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index f4d4824..b24ce12 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -66,8 +66,6 @@
<dimen name="lockscreen_shade_qs_transition_distance">@dimen/lockscreen_shade_notifications_scrim_transition_distance</dimen>
<dimen name="lockscreen_shade_qs_transition_delay">@dimen/lockscreen_shade_notifications_scrim_transition_delay</dimen>
<dimen name="lockscreen_shade_qs_squish_transition_distance">@dimen/lockscreen_shade_qs_transition_distance</dimen>
- <!-- On split-shade, the QS squish transition should start from half height. -->
- <item name="lockscreen_shade_qs_squish_start_fraction" type="dimen" format="float" >0.5</item>
<!-- On split-shade, there should be no depth effect, so setting the value to 0. -->
<dimen name="lockscreen_shade_depth_controller_transition_distance">0dp</dimen>
<dimen name="lockscreen_shade_udfps_keyguard_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index a587e5a..5dcbeb5 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -86,8 +86,6 @@
<dimen name="lockscreen_shade_qs_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
<dimen name="lockscreen_shade_qs_transition_delay">@dimen/lockscreen_shade_scrim_transition_distance</dimen>
<dimen name="lockscreen_shade_qs_squish_transition_distance">@dimen/lockscreen_shade_qs_transition_distance</dimen>
- <!-- On large screen portrait, the QS squish transition should start from half height. -->
- <item name="lockscreen_shade_qs_squish_start_fraction" type="dimen" format="float" >0.5</item>
<dimen name="lockscreen_shade_depth_controller_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
<dimen name="lockscreen_shade_udfps_keyguard_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
<dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e09a6ee..f7019dc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1174,6 +1174,7 @@
<!-- Output switcher panel related dimensions -->
<dimen name="media_output_dialog_list_max_height">355dp</dimen>
+ <dimen name="media_output_dialog_list_item_height">76dp</dimen>
<dimen name="media_output_dialog_header_album_icon_size">72dp</dimen>
<dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
@@ -1221,7 +1222,7 @@
<!-- The fraction at which the QS "squish" transition should start during the lockscreen shade
expansion. 0 is fully collapsed, 1 is fully expanded. -->
- <item type="dimen" format="float" name="lockscreen_shade_qs_squish_start_fraction">0</item>
+ <item type="dimen" format="float" name="lockscreen_shade_qs_squish_start_fraction">0.5</item>
<!-- Distance that the full shade transition takes in order for depth of the wallpaper to fully
change. -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index f0210fd..5d6598d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -49,6 +49,8 @@
InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
public static final int CUJ_SPLIT_SCREEN_ENTER =
InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
+ public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
+ InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -57,6 +59,7 @@
CUJ_APP_CLOSE_TO_PIP,
CUJ_QUICK_SWITCH,
CUJ_APP_LAUNCH_FROM_WIDGET,
+ CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index f82e7db..b78fa9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -52,7 +52,6 @@
val becauseCannotSkipBouncer: Boolean,
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
- val bouncerIsOrWillShow: Boolean,
val faceAuthenticated: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f73c98e..2bdb1b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -22,8 +22,6 @@
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
import static java.lang.Integer.max;
@@ -87,8 +85,8 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.settings.GlobalSettings;
@@ -1098,6 +1096,7 @@
return;
}
+ mView.setAlpha(1f);
mUserSwitcherViewGroup.setAlpha(0f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
1f);
@@ -1137,7 +1136,7 @@
KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
- BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
+ BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
UserRecord item = getItem(position);
@@ -1172,8 +1171,7 @@
}
textView.setSelected(item == currentUser);
view.setEnabled(item.isSwitchToEnabled);
- view.setAlpha(view.isEnabled() ? USER_SWITCH_ENABLED_ALPHA :
- USER_SWITCH_DISABLED_ALPHA);
+ UserSwitcherController.setSelectableAlpha(view);
return view;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index d8cffd7..5995e85 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -107,6 +107,14 @@
}
@Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ if (mShowDefaultMessage) {
+ showDefaultMessage();
+ }
+ }
+
+ @Override
void resetState() {
super.resetState();
mStateMachine.reset();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 32c1cf9..6745cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2597,7 +2597,7 @@
// on bouncer if both fp and fingerprint are enrolled.
final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible
&& mDeviceInteractive && !mGoingToSleep
- && !statusBarShadeLocked && !mBouncerIsOrWillBeShowing;
+ && !statusBarShadeLocked && !mBouncerFullyShown;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
final boolean isLockDown =
@@ -2667,7 +2667,6 @@
becauseCannotSkipBouncer,
biometricEnabledForUser,
mBouncerFullyShown,
- mBouncerIsOrWillBeShowing,
faceAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
@@ -3243,8 +3242,7 @@
cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
}
}
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN);
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
if (wasBouncerFullyShown != mBouncerFullyShown) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 7fc8123..a5fdc68 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -103,7 +103,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -253,7 +252,6 @@
@Inject Lazy<UserInfoController> mUserInfoController;
@Inject Lazy<KeyguardStateController> mKeyguardMonitor;
@Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
- @Inject Lazy<BatteryController> mBatteryController;
@Inject Lazy<NightDisplayListener> mNightDisplayListener;
@Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController;
@Inject Lazy<ManagedProfileController> mManagedProfileController;
@@ -404,8 +402,6 @@
mProviders.put(UserInfoController.class, mUserInfoController::get);
- mProviders.put(BatteryController.class, mBatteryController::get);
-
mProviders.put(NightDisplayListener.class, mNightDisplayListener::get);
mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index fbfc94a..a996699 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -35,6 +35,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
@@ -126,6 +127,7 @@
PowerManager powerManager,
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController,
+ DumpManager dumpManager,
@Main Handler mainHandler,
@Background Handler bgHandler) {
BatteryController bC = new BatteryControllerImpl(
@@ -134,6 +136,7 @@
powerManager,
broadcastDispatcher,
demoModeController,
+ dumpManager,
mainHandler,
bgHandler);
bC.init();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0469152..443d277 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,6 +41,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.media.dagger.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
@@ -116,6 +117,7 @@
AppOpsModule.class,
AssistModule.class,
BiometricsModule.class,
+ BouncerViewModule.class,
ClockModule.class,
CoroutinesModule.class,
DreamModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index d7b7777..733a80d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -35,6 +35,7 @@
import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -73,6 +74,7 @@
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
private final Handler mHandler;
private final int mDreamOverlayMaxTranslationY;
+ private final BouncerCallbackInteractor mBouncerCallbackInteractor;
private long mJitterStartTimeMillis;
@@ -131,7 +133,8 @@
@Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
- @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) {
+ @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
+ BouncerCallbackInteractor bouncerCallbackInteractor) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
@@ -151,6 +154,7 @@
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
mMillisUntilFullJitter = millisUntilFullJitter;
+ mBouncerCallbackInteractor = bouncerCallbackInteractor;
}
@Override
@@ -167,6 +171,7 @@
if (bouncer != null) {
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
+ mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
@Override
@@ -176,6 +181,7 @@
if (bouncer != null) {
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
+ mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 96f77b3..696fc72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
@@ -26,11 +27,13 @@
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.ViewModelStore;
+import com.android.dream.lowlight.dagger.LowLightDreamModule;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
@@ -44,6 +47,7 @@
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* The {@link DreamOverlayService} is responsible for placing an overlay on top of a dream. The
@@ -62,6 +66,8 @@
// content area).
private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Nullable
+ private final ComponentName mLowLightDreamComponent;
private final UiEventLogger mUiEventLogger;
// A reference to the {@link Window} used to hold the dream overlay.
@@ -125,10 +131,13 @@
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
+ ComponentName lowLightDreamComponent) {
mContext = context;
mExecutor = executor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLowLightDreamComponent = lowLightDreamComponent;
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
@@ -155,6 +164,7 @@
windowManager.removeView(mWindow.getDecorView());
}
mStateController.setOverlayActive(false);
+ mStateController.setLowLightActive(false);
mDestroyed = true;
super.onDestroy();
}
@@ -163,6 +173,9 @@
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
setCurrentState(Lifecycle.State.STARTED);
+ final ComponentName dreamComponent = getDreamComponent();
+ mStateController.setLowLightActive(
+ dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
mExecutor.execute(() -> {
if (mDestroyed) {
// The task could still be executed after the service has been destroyed. Bail if
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 69e41ba..72feaca 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -50,6 +50,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
+ public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -193,6 +194,14 @@
return containsState(STATE_DREAM_OVERLAY_ACTIVE);
}
+ /**
+ * Returns whether low light mode is active.
+ * @return {@code true} if in low light mode, {@code false} otherwise.
+ */
+ public boolean isLowLightActive() {
+ return containsState(STATE_LOW_LIGHT_ACTIVE);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -222,6 +231,14 @@
}
/**
+ * Sets whether low light mode is active.
+ * @param active {@code true} if low light mode is active, {@code false} otherwise.
+ */
+ public void setLowLightActive(boolean active) {
+ modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index aa59cc6..bb1c430 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -20,7 +20,6 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
@@ -36,6 +35,8 @@
import android.util.PluralsMessageFormatter;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
@@ -73,6 +74,8 @@
private final Optional<DreamOverlayNotificationCountProvider>
mDreamOverlayNotificationCountProvider;
private final ZenModeController mZenModeController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final StatusBarWindowStateController mStatusBarWindowStateController;
private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
private final Executor mMainExecutor;
private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
@@ -102,6 +105,14 @@
}
};
+ private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ updateLowLightState();
+ }
+ };
+
private final IndividualSensorPrivacyController.Callback mSensorCallback =
(sensor, blocked) -> updateMicCameraBlockedStatusIcon();
@@ -140,7 +151,8 @@
Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,
ZenModeController zenModeController,
StatusBarWindowStateController statusBarWindowStateController,
- DreamOverlayStatusBarItemsProvider statusBarItemsProvider) {
+ DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
+ DreamOverlayStateController dreamOverlayStateController) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -151,8 +163,10 @@
mDateFormatUtil = dateFormatUtil;
mSensorPrivacyController = sensorPrivacyController;
mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider;
+ mStatusBarWindowStateController = statusBarWindowStateController;
mStatusBarItemsProvider = statusBarItemsProvider;
mZenModeController = zenModeController;
+ mDreamOverlayStateController = dreamOverlayStateController;
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -180,6 +194,9 @@
mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
+ mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
+ updateLowLightState();
+
mTouchInsetSession.addViewToTracking(mView);
}
@@ -193,6 +210,7 @@
provider -> provider.removeCallback(mNotificationCountCallback));
mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
mView.removeAllExtraStatusBarItemViews();
+ mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback);
mTouchInsetSession.clear();
mIsAttached = false;
@@ -217,6 +235,15 @@
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
+ private void updateLowLightState() {
+ int visibility = View.VISIBLE;
+ if (mDreamOverlayStateController.isLowLightActive()
+ || mStatusBarWindowStateController.windowIsShowing()) {
+ visibility = View.INVISIBLE;
+ }
+ mView.setVisibility(visibility);
+ }
+
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
final String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma";
final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
@@ -272,7 +299,7 @@
private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
mMainExecutor.execute(() -> {
- if (!mIsAttached) {
+ if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 2dd2098..f9dca08 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
+import com.android.dream.lowlight.dagger.LowLightDreamModule;
import com.android.settingslib.dream.DreamBackend;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -37,6 +38,7 @@
*/
@Module(includes = {
RegisteredComplicationsModule.class,
+ LowLightDreamModule.class,
},
subcomponents = {
DreamOverlayComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index ff53df3..48f5f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -104,6 +104,10 @@
public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
new UnreleasedFlag(209, true);
+ /** Whether the new implementation of UserSwitcherController should be used. */
+ public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER =
+ new UnreleasedFlag(210, false);
+
/***************************************/
// 300 - power menu
public static final ReleasedFlag POWER_MENU_LITE =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4214240..38b98eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
@@ -845,6 +846,8 @@
if (launchIsFullScreen) {
mCentralSurfaces.instantCollapseNotificationPanel();
}
+
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@NonNull
@@ -991,6 +994,8 @@
setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+ mOccluded);
+
+ mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
}
@Override
@@ -999,6 +1004,9 @@
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false.");
+ mInteractionJankMonitor.begin(
+ createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION)
+ .setTag("UNOCCLUDE"));
setOccluded(false /* isOccluded */, true /* animate */);
if (apps == null || apps.length == 0 || apps[0] == null) {
@@ -1057,6 +1065,8 @@
try {
finishedCallback.onAnimationFinished();
mUnoccludeAnimator = null;
+
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -2573,7 +2583,8 @@
};
try {
mInteractionJankMonitor.begin(
- createInteractionJankMonitorConf("RunRemoteAnimation"));
+ createInteractionJankMonitorConf(
+ CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RunRemoteAnimation"));
runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
wallpapers, nonApps, callback);
} catch (RemoteException e) {
@@ -2589,7 +2600,8 @@
mSurfaceBehindRemoteAnimationRunning = true;
mInteractionJankMonitor.begin(
- createInteractionJankMonitorConf("DismissPanel"));
+ createInteractionJankMonitorConf(
+ CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "DismissPanel"));
// Pass the surface and metadata to the unlock animation controller.
mKeyguardUnlockAnimationControllerLazy.get()
@@ -2597,7 +2609,8 @@
apps, startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
mInteractionJankMonitor.begin(
- createInteractionJankMonitorConf("RemoteAnimationDisabled"));
+ createInteractionJankMonitorConf(
+ CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
@@ -2677,10 +2690,15 @@
sendUserPresentBroadcast();
}
- private Configuration.Builder createInteractionJankMonitorConf(String tag) {
- return Configuration.Builder.withView(CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
- mKeyguardViewControllerLazy.get().getViewRootImpl().getView())
- .setTag(tag);
+ private Configuration.Builder createInteractionJankMonitorConf(int cuj) {
+ return createInteractionJankMonitorConf(cuj, null /* tag */);
+ }
+
+ private Configuration.Builder createInteractionJankMonitorConf(int cuj, @Nullable String tag) {
+ final Configuration.Builder builder = Configuration.Builder.withView(
+ cuj, mKeyguardViewControllerLazy.get().getViewRootImpl().getView());
+
+ return tag != null ? builder.setTag(tag) : builder;
}
/**
@@ -3291,6 +3309,10 @@
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
super.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
+ mInteractionJankMonitor.begin(
+ createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION)
+ .setTag("OCCLUDE"));
+
// This is the first signal we have from WM that we're going to be occluded. Set our
// internal state to reflect that immediately, vs. waiting for the launch animator to
// begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
@@ -3307,6 +3329,7 @@
+ "Setting occluded state to: " + isKeyguardOccluded);
setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
+ mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
new file mode 100644
index 0000000..99ae85d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.data
+
+import android.view.KeyEvent
+import com.android.systemui.dagger.SysUISingleton
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+
+/** An abstraction to interface with the ui layer, without changing state. */
+interface BouncerView {
+ var delegate: BouncerViewDelegate?
+}
+
+/** A lightweight class to hold reference to the ui delegate. */
+@SysUISingleton
+class BouncerViewImpl @Inject constructor() : BouncerView {
+ private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null)
+ override var delegate: BouncerViewDelegate?
+ get() = _delegate.get()
+ set(value) {
+ _delegate = WeakReference(value)
+ }
+}
+
+/** An abstraction that implements view logic. */
+interface BouncerViewDelegate {
+ fun isFullScreenBouncer(): Boolean
+ fun shouldDismissOnMenuPressed(): Boolean
+ fun interceptMediaKey(event: KeyEvent?): Boolean
+ fun dispatchBackKeyEventPreIme(): Boolean
+ fun showNextSecurityScreenOrFinish(): Boolean
+ fun resume()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
new file mode 100644
index 0000000..390c54e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface BouncerViewModule {
+ /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
+ @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
new file mode 100644
index 0000000..543389e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates app state for the lock screen bouncer. */
+@SysUISingleton
+class KeyguardBouncerRepository
+@Inject
+constructor(
+ private val viewMediatorCallback: ViewMediatorCallback,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+ var bouncerPromptReason: Int? = null
+ /** Determines if we want to instantaneously show the bouncer instead of translating. */
+ private val _isScrimmed = MutableStateFlow(false)
+ val isScrimmed = _isScrimmed.asStateFlow()
+ /** Set amount of how much of the bouncer is showing on the screen */
+ private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+ val expansionAmount = _expansionAmount.asStateFlow()
+ private val _isVisible = MutableStateFlow(false)
+ val isVisible = _isVisible.asStateFlow()
+ private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
+ val show = _show.asStateFlow()
+ private val _showingSoon = MutableStateFlow(false)
+ val showingSoon = _showingSoon.asStateFlow()
+ private val _hide = MutableStateFlow(false)
+ val hide = _hide.asStateFlow()
+ private val _startingToHide = MutableStateFlow(false)
+ val startingToHide = _startingToHide.asStateFlow()
+ private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
+ val onDismissAction = _onDismissAction.asStateFlow()
+ private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
+ val startingDisappearAnimation = _disappearAnimation.asStateFlow()
+ private val _keyguardPosition = MutableStateFlow(0f)
+ val keyguardPosition = _keyguardPosition.asStateFlow()
+ private val _resourceUpdateRequests = MutableStateFlow(false)
+ val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+ private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+ val showMessage = _showMessage.asStateFlow()
+ private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+ /** Determines if user is already unlocked */
+ val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+ private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+ val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+ private val _onScreenTurnedOff = MutableStateFlow(false)
+ val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+
+ val bouncerErrorMessage: CharSequence?
+ get() = viewMediatorCallback.consumeCustomMessage()
+
+ init {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
+ }
+
+ override fun onLockedOutStateChanged(type: BiometricSourceType) {
+ if (type == BiometricSourceType.FINGERPRINT) {
+ bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
+ }
+ }
+ }
+
+ keyguardUpdateMonitor.registerCallback(callback)
+ }
+
+ fun setScrimmed(isScrimmed: Boolean) {
+ _isScrimmed.value = isScrimmed
+ }
+
+ fun setExpansion(expansion: Float) {
+ _expansionAmount.value = expansion
+ }
+
+ fun setVisible(isVisible: Boolean) {
+ _isVisible.value = isVisible
+ }
+
+ fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+ _show.value = keyguardBouncerModel
+ }
+
+ fun setShowingSoon(showingSoon: Boolean) {
+ _showingSoon.value = showingSoon
+ }
+
+ fun setHide(hide: Boolean) {
+ _hide.value = hide
+ }
+
+ fun setStartingToHide(startingToHide: Boolean) {
+ _startingToHide.value = startingToHide
+ }
+
+ fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
+ _onDismissAction.value = bouncerCallbackActionsModel
+ }
+
+ fun setStartDisappearAnimation(runnable: Runnable?) {
+ _disappearAnimation.value = runnable
+ }
+
+ fun setKeyguardPosition(keyguardPosition: Float) {
+ _keyguardPosition.value = keyguardPosition
+ }
+
+ fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+ _resourceUpdateRequests.value = willUpdateResources
+ }
+
+ fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+ _showMessage.value = bouncerShowMessageModel
+ }
+
+ fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+ _keyguardAuthenticated.value = keyguardAuthenticated
+ }
+
+ fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+ _isBackButtonEnabled.value = isBackButtonEnabled
+ }
+
+ fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+ _onScreenTurnedOff.value = onScreenTurnedOff
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
new file mode 100644
index 0000000..10c7a37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
@@ -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.systemui.keyguard.domain.interactor
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/** Interactor to add and remove callbacks for the bouncer. */
+@SysUISingleton
+class BouncerCallbackInteractor @Inject constructor() {
+ private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
+ private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>()
+ /** Add a KeyguardResetCallback. */
+ fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+ resetCallbacks.addIfAbsent(callback)
+ }
+
+ /** Remove a KeyguardResetCallback. */
+ fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+ resetCallbacks.remove(callback)
+ }
+
+ /** Adds a callback to listen to bouncer expansion updates. */
+ fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+ if (!expansionCallbacks.contains(callback)) {
+ expansionCallbacks.add(callback)
+ }
+ }
+
+ /**
+ * Removes a previously added callback. If the callback was never added, this method does
+ * nothing.
+ */
+ fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+ expansionCallbacks.remove(callback)
+ }
+
+ /** Propagate fully shown to bouncer expansion callbacks. */
+ fun dispatchFullyShown() {
+ for (callback in expansionCallbacks) {
+ callback.onFullyShown()
+ }
+ }
+
+ /** Propagate starting to hide to bouncer expansion callbacks. */
+ fun dispatchStartingToHide() {
+ for (callback in expansionCallbacks) {
+ callback.onStartingToHide()
+ }
+ }
+
+ /** Propagate starting to show to bouncer expansion callbacks. */
+ fun dispatchStartingToShow() {
+ for (callback in expansionCallbacks) {
+ callback.onStartingToShow()
+ }
+ }
+
+ /** Propagate fully hidden to bouncer expansion callbacks. */
+ fun dispatchFullyHidden() {
+ for (callback in expansionCallbacks) {
+ callback.onFullyHidden()
+ }
+ }
+
+ /** Propagate expansion changes to bouncer expansion callbacks. */
+ fun dispatchExpansionChanged(expansion: Float) {
+ for (callback in expansionCallbacks) {
+ callback.onExpansionChanged(expansion)
+ }
+ }
+ /** Propagate visibility changes to bouncer expansion callbacks. */
+ fun dispatchVisibilityChanged(visibility: Int) {
+ for (callback in expansionCallbacks) {
+ callback.onVisibilityChanged(visibility == View.VISIBLE)
+ }
+ }
+
+ /** Propagate keyguard reset. */
+ fun dispatchReset() {
+ for (callback in resetCallbacks) {
+ callback.onKeyguardReset()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
new file mode 100644
index 0000000..7d4db37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.res.ColorStateList
+import android.os.Handler
+import android.os.Trace
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/** Encapsulates business logic for interacting with the lock-screen bouncer. */
+@SysUISingleton
+class BouncerInteractor
+@Inject
+constructor(
+ private val repository: KeyguardBouncerRepository,
+ private val bouncerView: BouncerView,
+ @Main private val mainHandler: Handler,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSecurityModel: KeyguardSecurityModel,
+ private val callbackInteractor: BouncerCallbackInteractor,
+ private val falsingCollector: FalsingCollector,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ keyguardBypassController: KeyguardBypassController,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+ /** Whether we want to wait for face auth. */
+ private val bouncerFaceDelay =
+ keyguardStateController.isFaceAuthEnabled &&
+ !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+ KeyguardUpdateMonitor.getCurrentUser()
+ ) &&
+ !needsFullscreenBouncer() &&
+ !keyguardUpdateMonitor.userNeedsStrongAuth() &&
+ !keyguardBypassController.bypassEnabled
+
+ /** Runnable to show the bouncer. */
+ val showRunnable = Runnable {
+ repository.setVisible(true)
+ repository.setShow(
+ KeyguardBouncerModel(
+ promptReason = repository.bouncerPromptReason ?: 0,
+ errorMessage = repository.bouncerErrorMessage,
+ expansionAmount = repository.expansionAmount.value
+ )
+ )
+ repository.setShowingSoon(false)
+ }
+
+ val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+ val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
+ val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull()
+ val hide: Flow<Unit> = repository.hide.filter { it }.map {}
+ val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
+ val isVisible: Flow<Boolean> = repository.isVisible
+ val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
+ val expansionAmount: Flow<Float> = repository.expansionAmount
+ val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
+ val startingDisappearAnimation: Flow<Runnable> =
+ repository.startingDisappearAnimation.filterNotNull()
+ val onDismissAction: Flow<BouncerCallbackActionsModel> =
+ repository.onDismissAction.filterNotNull()
+ val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
+ val keyguardPosition: Flow<Float> = repository.keyguardPosition
+
+ // TODO(b/243685699): Move isScrimmed logic to data layer.
+ // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
+ /** Show the bouncer if necessary and set the relevant states. */
+ @JvmOverloads
+ fun show(isScrimmed: Boolean) {
+ // Reset some states as we show the bouncer.
+ repository.setShowMessage(null)
+ repository.setOnScreenTurnedOff(false)
+ repository.setKeyguardAuthenticated(null)
+ repository.setHide(false)
+ repository.setStartingToHide(false)
+
+ val resumeBouncer =
+ (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer()
+
+ if (!resumeBouncer && repository.show.value != null) {
+ // If bouncer is visible, the bouncer is already showing.
+ return
+ }
+
+ val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
+ if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
+ // In split system user mode, we never unlock system user.
+ return
+ }
+
+ Trace.beginSection("KeyguardBouncer#show")
+ repository.setScrimmed(isScrimmed)
+ if (isScrimmed) {
+ setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ }
+
+ if (resumeBouncer) {
+ bouncerView.delegate?.resume()
+ // Bouncer is showing the next security screen and we just need to prompt a resume.
+ return
+ }
+ if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+ // Keyguard is done.
+ return
+ }
+
+ repository.setShowingSoon(true)
+ if (bouncerFaceDelay) {
+ mainHandler.postDelayed(showRunnable, 1200L)
+ } else {
+ DejankUtils.postAfterTraversal(showRunnable)
+ }
+ keyguardStateController.notifyBouncerShowing(true)
+ callbackInteractor.dispatchStartingToShow()
+
+ Trace.endSection()
+ }
+
+ /** Sets the correct bouncer states to hide the bouncer. */
+ fun hide() {
+ Trace.beginSection("KeyguardBouncer#hide")
+ if (isFullyShowing()) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+ SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+ )
+ dismissCallbackRegistry.notifyDismissCancelled()
+ }
+
+ falsingCollector.onBouncerHidden()
+ keyguardStateController.notifyBouncerShowing(false /* showing */)
+ cancelShowRunnable()
+ repository.setShowingSoon(false)
+ repository.setOnDismissAction(null)
+ repository.setVisible(false)
+ repository.setHide(true)
+ repository.setShow(null)
+ Trace.endSection()
+ }
+
+ /**
+ * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
+ * where 0f => showing and 1f => hiding
+ */
+ fun setExpansion(expansion: Float) {
+ val oldExpansion = repository.expansionAmount.value
+ val expansionChanged = oldExpansion != expansion
+ if (repository.startingDisappearAnimation.value == null) {
+ repository.setExpansion(expansion)
+ }
+
+ if (
+ expansion == KeyguardBouncer.EXPANSION_VISIBLE &&
+ oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
+ ) {
+ falsingCollector.onBouncerShown()
+ callbackInteractor.dispatchFullyShown()
+ } else if (
+ expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
+ oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
+ ) {
+ repository.setVisible(false)
+ repository.setShow(null)
+ falsingCollector.onBouncerHidden()
+ DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() }
+ callbackInteractor.dispatchFullyHidden()
+ } else if (
+ expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
+ oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
+ ) {
+ callbackInteractor.dispatchStartingToHide()
+ repository.setStartingToHide(true)
+ }
+ if (expansionChanged) {
+ callbackInteractor.dispatchExpansionChanged(expansion)
+ }
+ }
+
+ /** Set the initial keyguard message to show when bouncer is shown. */
+ fun showMessage(message: String?, colorStateList: ColorStateList?) {
+ repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
+ }
+
+ /**
+ * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
+ * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
+ * call cancelAction.
+ */
+ fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?
+ ) {
+ repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ }
+
+ /** Update the resources of the views. */
+ fun updateResources() {
+ repository.setResourceUpdateRequests(true)
+ }
+
+ /** Tell the bouncer that keyguard is authenticated. */
+ fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
+ repository.setKeyguardAuthenticated(strongAuth)
+ }
+
+ /** Tell the bouncer the screen has turned off. */
+ fun onScreenTurnedOff() {
+ repository.setOnScreenTurnedOff(true)
+ }
+
+ /** Update the position of the bouncer when showing. */
+ fun setKeyguardPosition(position: Float) {
+ repository.setKeyguardPosition(position)
+ }
+
+ /** Notifies that the state change was handled. */
+ fun notifyKeyguardAuthenticatedHandled() {
+ repository.setKeyguardAuthenticated(null)
+ }
+
+ /** Notify that view visibility has changed. */
+ fun notifyBouncerVisibilityHasChanged(visibility: Int) {
+ callbackInteractor.dispatchVisibilityChanged(visibility)
+ }
+
+ /** Notify that the resources have been updated */
+ fun notifyUpdatedResources() {
+ repository.setResourceUpdateRequests(false)
+ }
+
+ /** Set whether back button is enabled when on the bouncer screen. */
+ fun setBackButtonEnabled(enabled: Boolean) {
+ repository.setIsBackButtonEnabled(enabled)
+ }
+
+ /** Tell the bouncer to start the pre hide animation. */
+ fun startDisappearAnimation(runnable: Runnable) {
+ val finishRunnable = Runnable {
+ repository.setStartDisappearAnimation(null)
+ runnable.run()
+ }
+ repository.setStartDisappearAnimation(finishRunnable)
+ }
+
+ /** Returns whether bouncer is fully showing. */
+ fun isFullyShowing(): Boolean {
+ return (repository.showingSoon.value || repository.isVisible.value) &&
+ repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+ repository.startingDisappearAnimation.value == null
+ }
+
+ /** Returns whether bouncer is scrimmed. */
+ fun isScrimmed(): Boolean {
+ return repository.isScrimmed.value
+ }
+
+ /** If bouncer expansion is between 0f and 1f non-inclusive. */
+ fun isInTransit(): Boolean {
+ return repository.showingSoon.value ||
+ repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+ repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+ }
+
+ /** Return whether bouncer is animating away. */
+ fun isAnimatingAway(): Boolean {
+ return repository.startingDisappearAnimation.value != null
+ }
+
+ /** Return whether bouncer will dismiss with actions */
+ fun willDismissWithAction(): Boolean {
+ return repository.onDismissAction.value?.onDismissAction != null
+ }
+
+ /** Returns whether the bouncer should be full screen. */
+ private fun needsFullscreenBouncer(): Boolean {
+ val mode: KeyguardSecurityModel.SecurityMode =
+ keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+ return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+ mode == KeyguardSecurityModel.SecurityMode.SimPuk
+ }
+
+ /** Remove the show runnable from the main handler queue to improve performance. */
+ private fun cancelShowRunnable() {
+ DejankUtils.removeCallbacks(showRunnable)
+ mainHandler.removeCallbacks(showRunnable)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt
new file mode 100644
index 0000000..81cf5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.plugins.ActivityStarter
+
+/** Encapsulates callbacks to be invoked by the bouncer logic. */
+// TODO(b/243683121): Move dismiss logic from view controllers
+data class BouncerCallbackActionsModel(
+ val onDismissAction: ActivityStarter.OnDismissAction?,
+ val cancelAction: Runnable?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
new file mode 100644
index 0000000..05cdeaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.content.res.ColorStateList
+
+/** Show a keyguard message to the bouncer. */
+data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
new file mode 100644
index 0000000..ad783da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Models the state of the lock-screen bouncer */
+data class KeyguardBouncerModel(
+ val promptReason: Int = 0,
+ val errorMessage: CharSequence? = null,
+ val expansionAmount: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
new file mode 100644
index 0000000..df26014
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.policy.SystemBarUtils
+import com.android.keyguard.KeyguardHostViewController
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+/** Binds the bouncer container to its view model. */
+object KeyguardBouncerViewBinder {
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: KeyguardBouncerViewModel,
+ componentFactory: KeyguardBouncerComponent.Factory
+ ) {
+ // Builds the KeyguardHostViewController from bouncer view group.
+ val hostViewController: KeyguardHostViewController =
+ componentFactory.create(view).keyguardHostViewController
+ hostViewController.init()
+ val delegate =
+ object : BouncerViewDelegate {
+ override fun isFullScreenBouncer(): Boolean {
+ val mode = hostViewController.currentSecurityMode
+ return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+ mode == KeyguardSecurityModel.SecurityMode.SimPuk
+ }
+
+ override fun shouldDismissOnMenuPressed(): Boolean {
+ return hostViewController.shouldEnableMenuKey()
+ }
+
+ override fun interceptMediaKey(event: KeyEvent?): Boolean {
+ return hostViewController.interceptMediaKey(event)
+ }
+
+ override fun dispatchBackKeyEventPreIme(): Boolean {
+ return hostViewController.dispatchBackKeyEventPreIme()
+ }
+
+ override fun showNextSecurityScreenOrFinish(): Boolean {
+ return hostViewController.dismiss(KeyguardUpdateMonitor.getCurrentUser())
+ }
+
+ override fun resume() {
+ hostViewController.showPrimarySecurityScreen()
+ hostViewController.onResume()
+ }
+ }
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ try {
+ viewModel.setBouncerViewDelegate(delegate)
+ launch {
+ viewModel.show.collect {
+ hostViewController.showPrimarySecurityScreen()
+ hostViewController.appear(
+ SystemBarUtils.getStatusBarHeight(view.context)
+ )
+ }
+ }
+
+ launch {
+ viewModel.showPromptReason.collect { prompt ->
+ hostViewController.showPromptReason(prompt)
+ }
+ }
+
+ launch {
+ viewModel.showBouncerErrorMessage.collect { errorMessage ->
+ hostViewController.showErrorMessage(errorMessage)
+ }
+ }
+
+ launch {
+ viewModel.showWithFullExpansion.collect { model ->
+ hostViewController.resetSecurityContainer()
+ hostViewController.showPromptReason(model.promptReason)
+ hostViewController.onResume()
+ }
+ }
+
+ launch {
+ viewModel.hide.collect {
+ hostViewController.cancelDismissAction()
+ hostViewController.cleanUp()
+ hostViewController.resetSecurityContainer()
+ }
+ }
+
+ launch {
+ viewModel.startingToHide.collect { hostViewController.onStartingToHide() }
+ }
+
+ launch {
+ viewModel.setDismissAction.collect {
+ hostViewController.setOnDismissAction(
+ it.onDismissAction,
+ it.cancelAction
+ )
+ }
+ }
+
+ launch {
+ viewModel.startDisappearAnimation.collect {
+ hostViewController.startDisappearAnimation(it)
+ }
+ }
+
+ launch {
+ viewModel.bouncerExpansionAmount.collect { expansion ->
+ hostViewController.setExpansion(expansion)
+ }
+ }
+
+ launch {
+ viewModel.bouncerExpansionAmount
+ .filter { it == EXPANSION_VISIBLE }
+ .collect {
+ hostViewController.onResume()
+ view.announceForAccessibility(
+ hostViewController.accessibilityTitleForCurrentMode
+ )
+ }
+ }
+
+ launch {
+ viewModel.isBouncerVisible.collect { isVisible ->
+ val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ view.visibility = visibility
+ hostViewController.onBouncerVisibilityChanged(visibility)
+ viewModel.notifyBouncerVisibilityHasChanged(visibility)
+ }
+ }
+
+ launch {
+ viewModel.isBouncerVisible
+ .filter { !it }
+ .collect {
+ // Remove existing input for security reasons.
+ hostViewController.resetSecurityContainer()
+ }
+ }
+
+ launch {
+ viewModel.keyguardPosition.collect { position ->
+ hostViewController.updateKeyguardPosition(position)
+ }
+ }
+
+ launch {
+ viewModel.updateResources.collect {
+ hostViewController.updateResources()
+ viewModel.notifyUpdateResources()
+ }
+ }
+
+ launch {
+ viewModel.bouncerShowMessage.collect {
+ hostViewController.showMessage(it.message, it.colorStateList)
+ }
+ }
+
+ launch {
+ viewModel.keyguardAuthenticated.collect {
+ hostViewController.finish(it, KeyguardUpdateMonitor.getCurrentUser())
+ viewModel.notifyKeyguardAuthenticated()
+ }
+ }
+
+ launch {
+ viewModel
+ .observeOnIsBackButtonEnabled { view.systemUiVisibility }
+ .collect { view.systemUiVisibility = it }
+ }
+
+ launch {
+ viewModel.screenTurnedOff.collect {
+ if (view.visibility == View.VISIBLE) {
+ hostViewController.onPause()
+ }
+ }
+ }
+ awaitCancellation()
+ } finally {
+ viewModel.setBouncerViewDelegate(null)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
new file mode 100644
index 0000000..9ad5211
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.view.View
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/** Models UI state for the lock screen bouncer; handles user input. */
+class KeyguardBouncerViewModel
+@Inject
+constructor(
+ private val view: BouncerView,
+ private val interactor: BouncerInteractor,
+) {
+ /** Observe on bouncer expansion amount. */
+ val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+
+ /** Observe on bouncer visibility. */
+ val isBouncerVisible: Flow<Boolean> = interactor.isVisible
+
+ /** Observe whether bouncer is showing. */
+ val show: Flow<KeyguardBouncerModel> = interactor.show
+
+ /** Observe bouncer prompt when bouncer is showing. */
+ val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason }
+
+ /** Observe bouncer error message when bouncer is showing. */
+ val showBouncerErrorMessage: Flow<CharSequence> =
+ interactor.show.map { it.errorMessage }.filterNotNull()
+
+ /** Observe visible expansion when bouncer is showing. */
+ val showWithFullExpansion: Flow<KeyguardBouncerModel> =
+ interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
+
+ /** Observe whether bouncer is hiding. */
+ val hide: Flow<Unit> = interactor.hide
+
+ /** Observe whether bouncer is starting to hide. */
+ val startingToHide: Flow<Unit> = interactor.startingToHide
+
+ /** Observe whether we want to set the dismiss action to the bouncer. */
+ val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
+
+ /** Observe whether we want to start the disappear animation. */
+ val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
+
+ /** Observe whether we want to update keyguard position. */
+ val keyguardPosition: Flow<Float> = interactor.keyguardPosition
+
+ /** Observe whether we want to update resources. */
+ val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests
+
+ /** Observe whether we want to set a keyguard message when the bouncer shows. */
+ val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
+
+ /** Observe whether keyguard is authenticated already. */
+ val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
+
+ /** Observe whether screen is turned off. */
+ val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
+
+ /** Notify that view visibility has changed. */
+ fun notifyBouncerVisibilityHasChanged(visibility: Int) {
+ return interactor.notifyBouncerVisibilityHasChanged(visibility)
+ }
+ /** Observe whether we want to update resources. */
+ fun notifyUpdateResources() {
+ interactor.notifyUpdatedResources()
+ }
+
+ /** Notify that keyguard authenticated was handled */
+ fun notifyKeyguardAuthenticated() {
+ interactor.notifyKeyguardAuthenticatedHandled()
+ }
+
+ /** Observe whether back button is enabled. */
+ fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
+ return interactor.isBackButtonEnabled.map { enabled ->
+ var vis: Int = systemUiVisibility()
+ vis =
+ if (enabled) {
+ vis and View.STATUS_BAR_DISABLE_BACK.inv()
+ } else {
+ vis or View.STATUS_BAR_DISABLE_BACK
+ }
+ vis
+ }
+ }
+
+ /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */
+ fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) {
+ view.delegate = delegate
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 267c1f5..b3a4ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -188,24 +188,28 @@
@AnyThread
fun start() = bgExecutor.execute {
- localMediaManager.registerCallback(this)
- localMediaManager.startScan()
- muteAwaitConnectionManager?.startListening()
- playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
- controller?.registerCallback(this)
- updateCurrent()
- started = true
- configurationController.addCallback(configListener)
+ if (!started) {
+ localMediaManager.registerCallback(this)
+ localMediaManager.startScan()
+ muteAwaitConnectionManager?.startListening()
+ playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+ controller?.registerCallback(this)
+ updateCurrent()
+ started = true
+ configurationController.addCallback(configListener)
+ }
}
@AnyThread
fun stop() = bgExecutor.execute {
- started = false
- controller?.unregisterCallback(this)
- localMediaManager.stopScan()
- localMediaManager.unregisterCallback(this)
- muteAwaitConnectionManager?.stopListening()
- configurationController.removeCallback(configListener)
+ if (started) {
+ started = false
+ controller?.unregisterCallback(this)
+ localMediaManager.stopScan()
+ localMediaManager.unregisterCallback(this)
+ muteAwaitConnectionManager?.stopListening()
+ configurationController.removeCallback(configListener)
+ }
}
fun dump(pw: PrintWriter) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 85d8f3f..a9e1a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -99,6 +99,7 @@
private Button mStopButton;
private Button mAppButton;
private int mListMaxHeight;
+ private int mItemHeight;
private WallpaperColors mWallpaperColors;
private Executor mExecutor;
private boolean mShouldLaunchLeBroadcastDialog;
@@ -106,10 +107,12 @@
MediaOutputBaseAdapter mAdapter;
private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
+ ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
+ int totalItemsHeight = mAdapter.getItemCount() * mItemHeight;
+ int correctHeight = Math.min(totalItemsHeight, mListMaxHeight);
// Set max height for list
- if (mDeviceListLayout.getHeight() > mListMaxHeight) {
- ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
- params.height = mListMaxHeight;
+ if (correctHeight != params.height) {
+ params.height = correctHeight;
mDeviceListLayout.setLayoutParams(params);
}
};
@@ -212,6 +215,8 @@
mLayoutManager = new LayoutManagerWrapper(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_list_max_height);
+ mItemHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_list_item_height);
mExecutor = Executors.newSingleThreadExecutor();
}
@@ -246,8 +251,10 @@
mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
mDeviceListLayoutListener);
// Init device list
+ mLayoutManager.setAutoMeasureEnabled(true);
mDevicesRecyclerView.setLayoutManager(mLayoutManager);
mDevicesRecyclerView.setAdapter(mAdapter);
+ mDevicesRecyclerView.setHasFixedSize(false);
// Init header icon
mHeaderIcon.setOnClickListener(v -> onHeaderIconClick());
// Init bottom buttons
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 7b4ac12..19b401d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -125,13 +125,16 @@
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
- private boolean mIsRefreshing = false;
- private boolean mNeedRefresh = false;
+ @VisibleForTesting
+ boolean mIsRefreshing = false;
+ @VisibleForTesting
+ boolean mNeedRefresh = false;
private MediaController mMediaController;
@VisibleForTesting
Callback mCallback;
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
+ @VisibleForTesting
private MediaOutputMetricLogger mMetricLogger;
private int mCurrentState;
@@ -221,15 +224,7 @@
Log.d(TAG, "No media controller for " + mPackageName);
}
}
- if (mLocalMediaManager == null) {
- if (DEBUG) {
- Log.d(TAG, "No local media manager " + mPackageName);
- }
- return;
- }
mCallback = cb;
- mLocalMediaManager.unregisterCallback(this);
- mLocalMediaManager.stopScan();
mLocalMediaManager.registerCallback(this);
mLocalMediaManager.startScan();
}
@@ -251,10 +246,8 @@
if (mMediaController != null) {
mMediaController.unregisterCallback(mCb);
}
- if (mLocalMediaManager != null) {
- mLocalMediaManager.unregisterCallback(this);
- mLocalMediaManager.stopScan();
- }
+ mLocalMediaManager.unregisterCallback(this);
+ mLocalMediaManager.stopScan();
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
mMediaDevices.clear();
@@ -658,10 +651,6 @@
return mLocalMediaManager.getCurrentConnectedDevice();
}
- private MediaDevice getMediaDeviceById(String id) {
- return mLocalMediaManager.getMediaDeviceById(new ArrayList<>(mMediaDevices), id);
- }
-
boolean addDeviceToPlayMedia(MediaDevice device) {
mMetricLogger.logInteractionExpansion(device);
return mLocalMediaManager.addDeviceToPlayMedia(device);
@@ -683,10 +672,6 @@
return mLocalMediaManager.getDeselectableMediaDevice();
}
- void adjustSessionVolume(String sessionId, int volume) {
- mLocalMediaManager.adjustSessionVolume(sessionId, volume);
- }
-
void adjustSessionVolume(int volume) {
mLocalMediaManager.adjustSessionVolume(volume);
}
@@ -1013,7 +998,7 @@
return;
}
- if (newState == PlaybackState.STATE_STOPPED || newState == PlaybackState.STATE_PAUSED) {
+ if (newState == PlaybackState.STATE_STOPPED) {
mCallback.onMediaStoppedOrPaused();
}
mCurrentState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 448e180..184089f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -473,6 +473,8 @@
? Math.max(mMediaTotalBottomMargin - getPaddingBottom(), 0) : 0;
layoutParams.topMargin = mediaNeedsTopMargin() && !horizontal
? mMediaTopMargin : 0;
+ // Call setLayoutParams explicitly to ensure that requestLayout happens
+ hostView.setLayoutParams(layoutParams);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 205d565..f41b905 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -21,7 +21,6 @@
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
-import android.content.res.Configuration;
import android.view.MotionEvent;
import android.view.View;
@@ -61,17 +60,6 @@
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
- new QSPanel.OnConfigurationChangedListener() {
- @Override
- public void onConfigurationChange(Configuration newConfig) {
- mView.updateResources();
- if (mView.isListening()) {
- refreshAllTiles();
- }
- }
- };
-
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -130,7 +118,6 @@
if (mView.isListening()) {
refreshAllTiles();
}
- mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
switchTileLayout(true);
mBrightnessMirrorHandler.onQsPanelAttached();
@@ -147,11 +134,18 @@
@Override
protected void onViewDetached() {
mTunerService.removeTunable(mView);
- mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
mBrightnessMirrorHandler.onQsPanelDettached();
super.onViewDetached();
}
+ @Override
+ protected void onConfigurationChanged() {
+ mView.updateResources();
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+ }
+
/** */
public void setVisibility(int visibility) {
mView.setVisibility(visibility);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 6e4c858..a5c60a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -101,11 +101,11 @@
+ newConfig.windowConfiguration);
mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
mView.getDumpableTag());
- onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
mLastOrientation = newConfig.orientation;
switchTileLayout(false);
}
+ onConfigurationChanged();
}
};
@@ -422,6 +422,8 @@
}
if (mMediaHost != null) {
pw.println(" media bounds: " + mMediaHost.getCurrentBounds());
+ pw.println(" horizontal layout: " + mUsingHorizontalLayout);
+ pw.println(" last orientation: " + mLastOrientation);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index c86e6e8..7ce0ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -46,14 +46,6 @@
@QSScope
public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
- private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
- newConfig -> {
- int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles);
- if (newMaxTiles != mView.getNumQuickTiles()) {
- setMaxTiles(newMaxTiles);
- }
- };
-
private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
@Inject
@@ -99,13 +91,11 @@
@Override
protected void onViewAttached() {
super.onViewAttached();
- mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
}
@Override
protected void onViewDetached() {
super.onViewDetached();
- mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
}
private void setMaxTiles(int parseNumTiles) {
@@ -115,6 +105,10 @@
@Override
protected void onConfigurationChanged() {
+ int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles);
+ if (newMaxTiles != mView.getNumQuickTiles()) {
+ setMaxTiles(newMaxTiles);
+ }
updateMediaExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 0ec4eef..97476b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -16,9 +16,6 @@
package com.android.systemui.qs.tiles;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -42,6 +39,7 @@
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
@@ -73,7 +71,8 @@
mAdapter.refresh();
}
- public static class Adapter extends UserSwitcherController.BaseUserAdapter
+ /** Provides views for user detail items. */
+ public static class Adapter extends BaseUserSwitcherAdapter
implements OnClickListener {
private final Context mContext;
@@ -137,7 +136,7 @@
v.setActivated(item.isCurrent);
v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
v.setEnabled(item.isSwitchToEnabled);
- v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+ UserSwitcherController.setSelectableAlpha(v);
if (item.isCurrent) {
mCurrentUserView = v;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 8d74a09..6be9bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -31,10 +31,15 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -108,7 +113,10 @@
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
AmbientState ambientState,
- PulsingGestureListener pulsingGestureListener
+ PulsingGestureListener pulsingGestureListener,
+ FeatureFlags featureFlags,
+ KeyguardBouncerViewModel keyguardBouncerViewModel,
+ KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory
) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -130,6 +138,12 @@
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
+ if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) {
+ KeyguardBouncerViewBinder.bind(
+ mView.findViewById(R.id.keyguard_bouncer_container),
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index e06c977..073ab8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1199,7 +1199,8 @@
return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
&& msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
- || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED);
+ || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
+ || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
}
private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
deleted file mode 100644
index 4551807..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 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.content.DialogInterface;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-
-public class UserUtil {
- public static void deleteUserWithPrompt(Context context, int userId,
- UserSwitcherController userSwitcherController) {
- new RemoveUserDialog(context, userId, userSwitcherController).show();
- }
-
- private final static class RemoveUserDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
-
- private final int mUserId;
- private final UserSwitcherController mUserSwitcherController;
-
- public RemoveUserDialog(Context context, int userId,
- UserSwitcherController userSwitcherController) {
- super(context);
- setTitle(R.string.user_remove_user_title);
- setMessage(context.getString(R.string.user_remove_user_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(R.string.user_remove_user_remove), this);
- setCanceledOnTouchOutside(false);
- mUserId = userId;
- mUserSwitcherController = userSwitcherController;
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == BUTTON_NEUTRAL) {
- cancel();
- } else {
- dismiss();
- mUserSwitcherController.removeUserId(mUserId);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d380e9f..d61c51e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -56,7 +56,9 @@
/**
* A class which manages the bouncer on the lockscreen.
+ * @deprecated Use KeyguardBouncerRepository
*/
+@Deprecated
public class KeyguardBouncer {
private static final String TAG = "KeyguardBouncer";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 4d61689..00c3e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -33,6 +33,7 @@
import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.UserSwitcherActivity;
import com.android.systemui.util.ViewController;
@@ -49,7 +50,7 @@
private final ActivityStarter mActivityStarter;
private final FeatureFlags mFeatureFlags;
- private UserSwitcherController.BaseUserAdapter mUserListener;
+ private BaseUserSwitcherAdapter mUserListener;
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
@@ -135,7 +136,7 @@
final UserSwitcherController controller = mUserSwitcherController;
if (controller != null) {
- mUserListener = new UserSwitcherController.BaseUserAdapter(controller) {
+ mUserListener = new BaseUserSwitcherAdapter(controller) {
@Override
public void notifyDataSetChanged() {
mView.refreshContentDescription(getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7867147..e61794b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -46,6 +46,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
@@ -53,6 +54,12 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -123,6 +130,9 @@
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
+ private final BouncerCallbackInteractor mBouncerCallbackInteractor;
+ private final BouncerInteractor mBouncerInteractor;
+ private final BouncerViewDelegate mBouncerViewDelegate;
private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -197,7 +207,7 @@
private View mNotificationContainer;
- protected KeyguardBouncer mBouncer;
+ @Nullable protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
protected boolean mRemoteInputActive;
@@ -223,6 +233,7 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+ private boolean mIsModernBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -237,6 +248,7 @@
private final DockManager mDockManager;
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
private final LatencyTracker mLatencyTracker;
+ private final KeyguardSecurityModel mKeyguardSecurityModel;
private KeyguardBypassController mBypassController;
@Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
@@ -271,7 +283,12 @@
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
- LatencyTracker latencyTracker) {
+ LatencyTracker latencyTracker,
+ KeyguardSecurityModel keyguardSecurityModel,
+ FeatureFlags featureFlags,
+ BouncerCallbackInteractor bouncerCallbackInteractor,
+ BouncerInteractor bouncerInteractor,
+ BouncerView bouncerView) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -288,8 +305,13 @@
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
+ mKeyguardSecurityModel = keyguardSecurityModel;
+ mBouncerCallbackInteractor = bouncerCallbackInteractor;
+ mBouncerInteractor = bouncerInteractor;
+ mBouncerViewDelegate = bouncerView.getDelegate();
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
+ mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
}
@Override
@@ -303,7 +325,11 @@
mBiometricUnlockController = biometricUnlockController;
ViewGroup container = mCentralSurfaces.getBouncerContainer();
- mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+ if (mIsModernBouncerEnabled) {
+ mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
+ } else {
+ mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+ }
mNotificationPanelViewController = notificationPanelViewController;
if (panelExpansionStateManager != null) {
panelExpansionStateManager.addExpansionListener(this);
@@ -377,29 +403,45 @@
if (mDozing && !mPulsing) {
return;
} else if (mNotificationPanelViewController.isUnlockHintRunning()) {
- mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ if (mBouncer != null) {
+ mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ }
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
} else if (bouncerNeedsScrimming()) {
- mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ if (mBouncer != null) {
+ mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ }
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else if (mShowing && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
&& !mCentralSurfaces.isInLaunchTransition()
&& !isUnlockCollapsing()) {
- mBouncer.setExpansion(fraction);
+ if (mBouncer != null) {
+ mBouncer.setExpansion(fraction);
+ }
+ mBouncerInteractor.setExpansion(fraction);
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
- && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) {
- mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+ && !bouncerIsShowing()
+ && !bouncerIsAnimatingAway()) {
+ if (mBouncer != null) {
+ mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+ }
+ mBouncerInteractor.show(/* isScrimmed= */false);
}
- } else if (!mShowing && mBouncer.inTransit()) {
+ } else if (!mShowing && isBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
- mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ if (mBouncer != null) {
+ mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ }
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -440,15 +482,20 @@
* {@link KeyguardBouncer#needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
- if (mBouncer.needsFullscreenBouncer() && !mDozing) {
+ if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
mCentralSurfaces.hideKeyguard();
- mBouncer.show(true /* resetSecuritySelection */);
+ if (mBouncer != null) {
+ mBouncer.show(true /* resetSecuritySelection */);
+ }
+ mBouncerInteractor.show(true);
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
hideBouncer(false /* destroyView */);
- mBouncer.prepare();
+ if (mBouncer != null) {
+ mBouncer.prepare();
+ }
}
}
updateStates();
@@ -480,10 +527,10 @@
*/
@VisibleForTesting
void hideBouncer(boolean destroyView) {
- if (mBouncer == null) {
- return;
+ if (mBouncer != null) {
+ mBouncer.hide(destroyView);
}
- mBouncer.hide(destroyView);
+ mBouncerInteractor.hide();
if (mShowing) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
@@ -501,8 +548,11 @@
public void showBouncer(boolean scrimmed) {
resetAlternateAuth(false);
- if (mShowing && !mBouncer.isShowing()) {
- mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+ if (mShowing && !isBouncerShowing()) {
+ if (mBouncer != null) {
+ mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+ }
+ mBouncerInteractor.show(scrimmed);
}
updateStates();
}
@@ -535,7 +585,11 @@
// instead of the bouncer.
if (shouldShowAltAuth()) {
if (!afterKeyguardGone) {
- mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+ if (mBouncer != null) {
+ mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+ mKeyguardGoneCancelAction);
+ }
+ mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
@@ -549,12 +603,18 @@
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- mBouncer.show(false /* resetSecuritySelection */);
+ mBouncerInteractor.show(/* isScrimmed= */true);
+ if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
- mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
+ mBouncerInteractor.setDismissAction(
+ mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+ mBouncerInteractor.show(/* isScrimmed= */true);
+ if (mBouncer != null) {
+ mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
+ mKeyguardGoneCancelAction);
+ }
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
@@ -591,7 +651,7 @@
// Hide bouncer and quick-quick settings.
if (mOccluded && !mDozing) {
mCentralSurfaces.hideKeyguard();
- if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
+ if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
hideBouncer(false /* destroyView */);
}
} else {
@@ -655,7 +715,10 @@
@Override
public void onFinishedGoingToSleep() {
- mBouncer.onScreenTurnedOff();
+ if (mBouncer != null) {
+ mBouncer.onScreenTurnedOff();
+ }
+ mBouncerInteractor.onScreenTurnedOff();
}
@Override
@@ -746,7 +809,7 @@
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
- if (animate && !mOccluded && mShowing && !mBouncer.isShowing()) {
+ if (animate && !mOccluded && mShowing && !bouncerIsShowing()) {
mCentralSurfaces.animateKeyguardUnoccluding();
}
}
@@ -762,8 +825,11 @@
@Override
public void startPreHideAnimation(Runnable finishRunnable) {
- if (mBouncer.isShowing()) {
- mBouncer.startPreHideAnimation(finishRunnable);
+ if (bouncerIsShowing()) {
+ if (mBouncer != null) {
+ mBouncer.startPreHideAnimation(finishRunnable);
+ }
+ mBouncerInteractor.startDisappearAnimation(finishRunnable);
mCentralSurfaces.onBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
@@ -873,8 +939,12 @@
}
public void onThemeChanged() {
- boolean wasShowing = mBouncer.isShowing();
- boolean wasScrimmed = mBouncer.isScrimmed();
+ if (mIsModernBouncerEnabled) {
+ updateResources();
+ return;
+ }
+ boolean wasShowing = bouncerIsShowing();
+ boolean wasScrimmed = bouncerIsScrimmed();
hideBouncer(true /* destroyView */);
mBouncer.prepare();
@@ -924,7 +994,12 @@
* WARNING: This method might cause Binder calls.
*/
public boolean isSecure() {
- return mBouncer.isSecure();
+ if (mBouncer != null) {
+ return mBouncer.isSecure();
+ }
+
+ return mKeyguardSecurityModel.getSecurityMode(
+ KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
}
@Override
@@ -941,10 +1016,11 @@
* @return whether the back press has been handled
*/
public boolean onBackPressed(boolean hideImmediately) {
- if (mBouncer.isShowing()) {
+ if (bouncerIsShowing()) {
mCentralSurfaces.endAffordanceLaunch();
// The second condition is for SIM card locked bouncer
- if (mBouncer.isScrimmed() && !mBouncer.needsFullscreenBouncer()) {
+ if (bouncerIsScrimmed()
+ && !needsFullscreenBouncer()) {
hideBouncer(false);
updateStates();
} else {
@@ -957,16 +1033,19 @@
@Override
public boolean isBouncerShowing() {
- return mBouncer.isShowing() || isShowingAlternateAuth();
+ return bouncerIsShowing() || isShowingAlternateAuth();
}
@Override
public boolean bouncerIsOrWillBeShowing() {
- return isBouncerShowing() || mBouncer.inTransit();
+ return isBouncerShowing() || isBouncerInTransit();
}
public boolean isFullscreenBouncer() {
- return mBouncer.isFullscreenBouncer();
+ if (mBouncerViewDelegate != null) {
+ return mBouncerViewDelegate.isFullScreenBouncer();
+ }
+ return mBouncer != null && mBouncer.isFullscreenBouncer();
}
/**
@@ -987,7 +1066,7 @@
private long getNavBarShowDelay() {
if (mKeyguardStateController.isKeyguardFadingAway()) {
return mKeyguardStateController.getKeyguardFadingAwayDelay();
- } else if (mBouncer.isShowing()) {
+ } else if (isBouncerShowing()) {
return NAV_BAR_SHOW_DELAY_BOUNCER;
} else {
// No longer dozing, or remote input is active. No delay.
@@ -1010,18 +1089,24 @@
protected void updateStates() {
boolean showing = mShowing;
boolean occluded = mOccluded;
- boolean bouncerShowing = mBouncer.isShowing();
+ boolean bouncerShowing = bouncerIsShowing();
boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
- boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
+ boolean bouncerDismissible = !isFullscreenBouncer();
boolean remoteInputActive = mRemoteInputActive;
if ((bouncerDismissible || !showing || remoteInputActive) !=
(mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
|| mFirstUpdate) {
if (bouncerDismissible || !showing || remoteInputActive) {
- mBouncer.setBackButtonEnabled(true);
+ if (mBouncer != null) {
+ mBouncer.setBackButtonEnabled(true);
+ }
+ mBouncerInteractor.setBackButtonEnabled(true);
} else {
- mBouncer.setBackButtonEnabled(false);
+ if (mBouncer != null) {
+ mBouncer.setBackButtonEnabled(false);
+ }
+ mBouncerInteractor.setBackButtonEnabled(false);
}
}
@@ -1098,7 +1183,9 @@
|| mPulsing && !mIsDocked)
&& mGesturalNav;
return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
- || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav
+ || bouncerIsShowing()
+ || mRemoteInputActive
+ || keyguardWithGestureNav
|| mGlobalActionsVisible);
}
@@ -1117,18 +1204,27 @@
}
public boolean shouldDismissOnMenuPressed() {
- return mBouncer.shouldDismissOnMenuPressed();
+ if (mBouncerViewDelegate != null) {
+ return mBouncerViewDelegate.shouldDismissOnMenuPressed();
+ }
+ return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
}
public boolean interceptMediaKey(KeyEvent event) {
- return mBouncer.interceptMediaKey(event);
+ if (mBouncerViewDelegate != null) {
+ return mBouncerViewDelegate.interceptMediaKey(event);
+ }
+ return mBouncer != null && mBouncer.interceptMediaKey(event);
}
/**
* @return true if the pre IME back event should be handled
*/
public boolean dispatchBackKeyEventPreIme() {
- return mBouncer.dispatchBackKeyEventPreIme();
+ if (mBouncerViewDelegate != null) {
+ return mBouncerViewDelegate.dispatchBackKeyEventPreIme();
+ }
+ return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
}
public void readyForKeyguardDone() {
@@ -1151,7 +1247,7 @@
}
public boolean isSecure(int userId) {
- return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId);
+ return isSecure() || mLockPatternUtils.isSecure(userId);
}
@Override
@@ -1174,7 +1270,10 @@
* fingerprint.
*/
public void notifyKeyguardAuthenticated(boolean strongAuth) {
- mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ if (mBouncer != null) {
+ mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ }
+ mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
resetAlternateAuth(false);
@@ -1189,7 +1288,10 @@
mKeyguardMessageAreaController.setMessage(message);
}
} else {
- mBouncer.showMessage(message, colorState);
+ if (mBouncer != null) {
+ mBouncer.showMessage(message, colorState);
+ }
+ mBouncerInteractor.showMessage(message, colorState);
}
}
@@ -1222,9 +1324,10 @@
public boolean bouncerNeedsScrimming() {
// When a dream overlay is active, scrimming will cause any expansion to immediately expand.
return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
- || mBouncer.willDismissWithAction()
- || (mBouncer.isShowing() && mBouncer.isScrimmed())
- || mBouncer.isFullscreenBouncer();
+ || bouncerWillDismissWithAction()
+ || (bouncerIsShowing()
+ && bouncerIsScrimmed())
+ || isFullscreenBouncer();
}
/**
@@ -1236,6 +1339,7 @@
if (mBouncer != null) {
mBouncer.updateResources();
}
+ mBouncerInteractor.updateResources();
}
public void dump(PrintWriter pw) {
@@ -1289,6 +1393,7 @@
}
}
+ @Nullable
public KeyguardBouncer getBouncer() {
return mBouncer;
}
@@ -1320,6 +1425,8 @@
if (mBouncer != null) {
mBouncer.updateKeyguardPosition(x);
}
+
+ mBouncerInteractor.setKeyguardPosition(x);
}
private static class DismissWithActionRequest {
@@ -1359,9 +1466,65 @@
* Returns if bouncer expansion is between 0 and 1 non-inclusive.
*/
public boolean isBouncerInTransit() {
- if (mBouncer == null) return false;
+ if (mBouncer != null) {
+ return mBouncer.inTransit();
+ }
- return mBouncer.inTransit();
+ return mBouncerInteractor.isInTransit();
+ }
+
+ /**
+ * Returns if bouncer is showing
+ */
+ public boolean bouncerIsShowing() {
+ if (mBouncer != null) {
+ return mBouncer.isShowing();
+ }
+
+ return mBouncerInteractor.isFullyShowing();
+ }
+
+ /**
+ * Returns if bouncer is scrimmed
+ */
+ public boolean bouncerIsScrimmed() {
+ if (mBouncer != null) {
+ return mBouncer.isScrimmed();
+ }
+
+ return mBouncerInteractor.isScrimmed();
+ }
+
+ /**
+ * Returns if bouncer is animating away
+ */
+ public boolean bouncerIsAnimatingAway() {
+ if (mBouncer != null) {
+ return mBouncer.isAnimatingAway();
+ }
+
+ return mBouncerInteractor.isAnimatingAway();
+ }
+
+ /**
+ * Returns if bouncer will dismiss with action
+ */
+ public boolean bouncerWillDismissWithAction() {
+ if (mBouncer != null) {
+ return mBouncer.willDismissWithAction();
+ }
+
+ return mBouncerInteractor.willDismissWithAction();
+ }
+
+ /**
+ * Returns if bouncer needs fullscreen bouncer. i.e. sim pin security method
+ */
+ public boolean needsFullscreenBouncer() {
+ KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
+ KeyguardUpdateMonitor.getCurrentUser());
+ return mode == KeyguardSecurityModel.SecurityMode.SimPin
+ || mode == KeyguardSecurityModel.SecurityMode.SimPuk;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
new file mode 100644
index 0000000..5b2d695
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.graphics.ColorFilter
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.widget.BaseAdapter
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserRecordName
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserSwitcherActionIconResourceId
+import java.lang.ref.WeakReference
+
+/** Provides views for user switcher experiences. */
+abstract class BaseUserSwitcherAdapter
+protected constructor(
+ protected val controller: UserSwitcherController,
+) : BaseAdapter() {
+
+ protected open val users: ArrayList<UserRecord>
+ get() = controller.users
+
+ init {
+ controller.addAdapter(WeakReference(this))
+ }
+
+ override fun getCount(): Int {
+ return if (controller.isKeyguardShowing) {
+ users.count { !it.isRestricted }
+ } else {
+ users.size
+ }
+ }
+
+ override fun getItem(position: Int): UserRecord {
+ return users[position]
+ }
+
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ /**
+ * Notifies that a user item in the UI has been clicked.
+ *
+ * If the user switcher is hosted in a dialog, passing a non-null [dialogShower] will allow
+ * animation to and from the parent dialog.
+ */
+ @JvmOverloads
+ fun onUserListItemClicked(
+ record: UserRecord,
+ dialogShower: DialogShower? = null,
+ ) {
+ controller.onUserListItemClicked(record, dialogShower)
+ }
+
+ open fun getName(context: Context, item: UserRecord): String {
+ return getName(context, item, false)
+ }
+
+ /** Returns the name for the given {@link UserRecord}. */
+ open fun getName(context: Context, item: UserRecord, isTablet: Boolean): String {
+ return getUserRecordName(
+ context = context,
+ record = item,
+ isGuestUserAutoCreated = controller.isGuestUserAutoCreated,
+ isGuestUserResetting = controller.isGuestUserResetting,
+ isTablet = isTablet,
+ )
+ }
+
+ fun refresh() {
+ controller.refreshUsers(UserHandle.USER_NULL)
+ }
+
+ companion object {
+ @JvmStatic
+ protected val disabledUserAvatarColorFilter: ColorFilter by lazy {
+ val matrix = ColorMatrix()
+ matrix.setSaturation(0f) // 0 - grayscale
+ ColorMatrixColorFilter(matrix)
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ protected fun getIconDrawable(
+ context: Context,
+ item: UserRecord,
+ isTablet: Boolean = false,
+ ): Drawable {
+ val iconRes =
+ getUserSwitcherActionIconResourceId(
+ item.isAddUser,
+ item.isGuest,
+ item.isAddSupervisedUser,
+ isTablet,
+ )
+ return checkNotNull(context.getDrawable(iconRes))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 753e940..149ed0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -19,14 +19,17 @@
import android.annotation.Nullable;
import android.view.View;
-import com.android.systemui.Dumpable;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-public interface BatteryController extends DemoMode, Dumpable,
+/**
+ * Controller for battery related information, including the charge level, power save mode,
+ * and time remaining information
+ */
+public interface BatteryController extends DemoMode,
CallbackController<BatteryStateChangeCallback> {
/**
* Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 33ddf7e..c7ad767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -38,11 +38,13 @@
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.util.Assert;
@@ -56,7 +58,8 @@
* Default implementation of a {@link BatteryController}. This controller monitors for battery
* level change events that are broadcasted by the system.
*/
-public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
+public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController,
+ Dumpable {
private static final String TAG = "BatteryController";
private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
@@ -70,6 +73,7 @@
private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
private final PowerManager mPowerManager;
private final DemoModeController mDemoModeController;
+ private final DumpManager mDumpManager;
private final Handler mMainHandler;
private final Handler mBgHandler;
protected final Context mContext;
@@ -101,6 +105,7 @@
PowerManager powerManager,
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController,
+ DumpManager dumpManager,
@Main Handler mainHandler,
@Background Handler bgHandler) {
mContext = context;
@@ -110,6 +115,7 @@
mEstimates = enhancedEstimates;
mBroadcastDispatcher = broadcastDispatcher;
mDemoModeController = demoModeController;
+ mDumpManager = dumpManager;
}
private void registerReceiver() {
@@ -134,6 +140,7 @@
}
}
mDemoModeController.addCallback(this);
+ mDumpManager.registerDumpable(TAG, this);
updatePowerSave();
updateEstimateInBackground();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 16306081..dc73d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -69,7 +69,7 @@
private final Context mContext;
private Resources mResources;
private final UserSwitcherController mUserSwitcherController;
- private UserSwitcherController.BaseUserAdapter mAdapter;
+ private BaseUserSwitcherAdapter mAdapter;
private final KeyguardStateController mKeyguardStateController;
private final FalsingManager mFalsingManager;
protected final SysuiStatusBarStateController mStatusBarStateController;
@@ -171,7 +171,7 @@
mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
mUserAvatarViewWithBackground = mView.findViewById(
R.id.kg_multi_user_avatar_with_background);
- mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
+ mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e2f5734..0995a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -16,9 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -232,14 +229,8 @@
}
/**
- * See:
+ * Returns {@code true} if the user switcher should be open by default on the lock screen.
*
- * <ul>
- * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
- * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
- * </ul>
- *
- * @return true if the user switcher should be open by default on the lock screen.
* @see android.os.UserManager#isUserSwitcherEnabled()
*/
public boolean isSimpleUserSwitcher() {
@@ -436,7 +427,7 @@
}
static class KeyguardUserAdapter extends
- UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+ BaseUserSwitcherAdapter implements View.OnClickListener {
private final Context mContext;
private final Resources mResources;
@@ -514,9 +505,9 @@
v.bind(name, drawable, item.info.id);
}
v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
+ v.setDisabledByAdmin(getController().isDisabledByAdmin(item));
v.setEnabled(item.isSwitchToEnabled);
- v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+ UserSwitcherController.setSelectableAlpha(v);
if (item.isCurrent) {
mCurrentUserView = v;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
new file mode 100644
index 0000000..843c232
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.policy
+
+import android.annotation.UserIdInt
+import android.content.Intent
+import android.view.View
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.Dumpable
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import java.lang.ref.WeakReference
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for a class that provides user switching functionality and state. */
+interface UserSwitcherController : Dumpable {
+
+ /** The current list of [UserRecord]. */
+ val users: ArrayList<UserRecord>
+
+ /** Whether the user switcher experience should use the simple experience. */
+ val isSimpleUserSwitcher: Boolean
+
+ /** Require a view for jank detection */
+ fun init(view: View)
+
+ /** The [UserRecord] of the current user or `null` when none. */
+ val currentUserRecord: UserRecord?
+
+ /** The name of the current user of the device or `null`, when none is selected. */
+ val currentUserName: String?
+
+ /**
+ * Notifies that a user has been selected.
+ *
+ * This will trigger the right user journeys to create a guest user, switch users, and/or
+ * navigate to the correct destination.
+ *
+ * If a user with the given ID is not found, this method is a no-op.
+ *
+ * @param userId The ID of the user to switch to.
+ * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
+ */
+ fun onUserSelected(userId: Int, dialogShower: DialogShower?)
+
+ /** Whether it is allowed to add users while the device is locked. */
+ val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+
+ /** Whether the guest user is configured to always be present on the device. */
+ val isGuestUserAutoCreated: Boolean
+
+ /** Whether the guest user is currently being reset. */
+ val isGuestUserResetting: Boolean
+
+ /** Creates and switches to the guest user. */
+ fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
+
+ /** Shows the add user dialog. */
+ fun showAddUserDialog(dialogShower: DialogShower?)
+
+ /** Starts an activity to add a supervised user to the device. */
+ fun startSupervisedUserActivity()
+
+ /** Notifies when the display density or font scale has changed. */
+ fun onDensityOrFontScaleChanged()
+
+ /** Registers an adapter to notify when the users change. */
+ fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+
+ /** Notifies the item for a user has been clicked. */
+ fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+
+ /**
+ * Removes guest user and switches to target user. The guest must be the current user and its id
+ * must be `guestUserId`.
+ *
+ * If `targetUserId` is `UserHandle.USER_NULL`, then create a new guest user in the foreground,
+ * and immediately switch to it. This is used for wiping the current guest and replacing it with
+ * a new one.
+ *
+ * If `targetUserId` is specified, then remove the guest in the background while switching to
+ * `targetUserId`.
+ *
+ * If device is configured with `config_guestUserAutoCreated`, then after guest user is removed,
+ * a new one is created in the background. This has no effect if `targetUserId` is
+ * `UserHandle.USER_NULL`.
+ *
+ * @param guestUserId id of the guest user to remove
+ * @param targetUserId id of the user to switch to after guest is removed. If
+ * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
+ */
+ fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+
+ /**
+ * Exits guest user and switches to previous non-guest user. The guest must be the current user.
+ *
+ * @param guestUserId user id of the guest user to exit
+ * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when
+ * target user id is not known
+ * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
+ * only if its ephemeral, else keep guest
+ */
+ fun exitGuestUser(
+ @UserIdInt guestUserId: Int,
+ @UserIdInt targetUserId: Int,
+ forceRemoveGuestOnExit: Boolean
+ )
+
+ /**
+ * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
+ * observer to wait until the device is provisioned, then schedule the guest creation.
+ */
+ fun schedulePostBootGuestCreation()
+
+ /** Whether keyguard is showing. */
+ val isKeyguardShowing: Boolean
+
+ /** Returns the [EnforcedAdmin] for the given record, or `null` if there isn't one. */
+ fun getEnforcedAdmin(record: UserRecord): EnforcedAdmin?
+
+ /** Returns `true` if the given record is disabled by the admin; `false` otherwise. */
+ fun isDisabledByAdmin(record: UserRecord): Boolean
+
+ /** Starts an activity with the given [Intent]. */
+ fun startActivity(intent: Intent)
+
+ /**
+ * Refreshes users from UserManager.
+ *
+ * The pictures are only loaded if they have not been loaded yet.
+ *
+ * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
+ */
+ fun refreshUsers(forcePictureLoadForId: Int)
+
+ /** Adds a subscriber to when user switches. */
+ fun addUserSwitchCallback(callback: UserSwitchCallback)
+
+ /** Removes a previously-added subscriber. */
+ fun removeUserSwitchCallback(callback: UserSwitchCallback)
+
+ /** Defines interface for classes that can be called back when the user is switched. */
+ fun interface UserSwitchCallback {
+ /** Notifies that the user has switched. */
+ fun onUserSwitched()
+ }
+
+ companion object {
+ /** Alpha value to apply to a user view in the user switcher when it's selectable. */
+ private const val ENABLED_ALPHA =
+ LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA
+
+ /** Alpha value to apply to a user view in the user switcher when it's not selectable. */
+ private const val DISABLED_ALPHA =
+ LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA
+
+ @JvmStatic
+ fun setSelectableAlpha(view: View) {
+ view.alpha =
+ if (view.isEnabled) {
+ ENABLED_ALPHA
+ } else {
+ DISABLED_ALPHA
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
new file mode 100644
index 0000000..12834f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -0,0 +1,269 @@
+/*
+ * 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.policy
+
+import android.content.Intent
+import android.view.View
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.user.data.source.UserRecord
+import dagger.Lazy
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Implementation of [UserSwitcherController]. */
+class UserSwitcherControllerImpl
+@Inject
+constructor(
+ private val flags: FeatureFlags,
+ @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
+) : UserSwitcherController {
+
+ private val isNewImpl: Boolean
+ get() = flags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER)
+ private val _oldImpl: UserSwitcherControllerOldImpl
+ get() = oldImpl.get()
+
+ private fun notYetImplemented(): Nothing {
+ error("Not yet implemented!")
+ }
+
+ override val users: ArrayList<UserRecord>
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.users
+ }
+
+ override val isSimpleUserSwitcher: Boolean
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.isSimpleUserSwitcher
+ }
+
+ override fun init(view: View) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.init(view)
+ }
+ }
+
+ override val currentUserRecord: UserRecord?
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.currentUserRecord
+ }
+
+ override val currentUserName: String?
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.currentUserName
+ }
+
+ override fun onUserSelected(
+ userId: Int,
+ dialogShower: UserSwitchDialogController.DialogShower?
+ ) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.onUserSelected(userId, dialogShower)
+ }
+ }
+
+ override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.isAddUsersFromLockScreenEnabled
+ }
+
+ override val isGuestUserAutoCreated: Boolean
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.isGuestUserAutoCreated
+ }
+
+ override val isGuestUserResetting: Boolean
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.isGuestUserResetting
+ }
+
+ override fun createAndSwitchToGuestUser(
+ dialogShower: UserSwitchDialogController.DialogShower?,
+ ) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.createAndSwitchToGuestUser(dialogShower)
+ }
+ }
+
+ override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.showAddUserDialog(dialogShower)
+ }
+ }
+
+ override fun startSupervisedUserActivity() {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.startSupervisedUserActivity()
+ }
+ }
+
+ override fun onDensityOrFontScaleChanged() {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.onDensityOrFontScaleChanged()
+ }
+ }
+
+ override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.addAdapter(adapter)
+ }
+ }
+
+ override fun onUserListItemClicked(
+ record: UserRecord,
+ dialogShower: UserSwitchDialogController.DialogShower?,
+ ) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.onUserListItemClicked(record, dialogShower)
+ }
+ }
+
+ override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.removeGuestUser(guestUserId, targetUserId)
+ }
+ }
+
+ override fun exitGuestUser(
+ guestUserId: Int,
+ targetUserId: Int,
+ forceRemoveGuestOnExit: Boolean
+ ) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+ }
+ }
+
+ override fun schedulePostBootGuestCreation() {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.schedulePostBootGuestCreation()
+ }
+ }
+
+ override val isKeyguardShowing: Boolean
+ get() =
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.isKeyguardShowing
+ }
+
+ override fun getEnforcedAdmin(record: UserRecord): RestrictedLockUtils.EnforcedAdmin? {
+ return if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.getEnforcedAdmin(record)
+ }
+ }
+
+ override fun isDisabledByAdmin(record: UserRecord): Boolean {
+ return if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.isDisabledByAdmin(record)
+ }
+ }
+
+ override fun startActivity(intent: Intent) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.startActivity(intent)
+ }
+ }
+
+ override fun refreshUsers(forcePictureLoadForId: Int) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.refreshUsers(forcePictureLoadForId)
+ }
+ }
+
+ override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.addUserSwitchCallback(callback)
+ }
+ }
+
+ override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.removeUserSwitchCallback(callback)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ if (isNewImpl) {
+ notYetImplemented()
+ } else {
+ _oldImpl.dump(pw, args)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
index 1d5b88e7..d365aa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
@@ -11,9 +11,8 @@
* 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
+ * limitations under the License.
*/
-
package com.android.systemui.statusbar.policy;
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
@@ -34,10 +33,6 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -51,7 +46,6 @@
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.WindowManagerGlobal;
-import android.widget.BaseAdapter;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -63,7 +57,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.Dumpable;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.R;
@@ -86,7 +79,6 @@
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.CreateUserActivity;
import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -106,15 +98,14 @@
import kotlinx.coroutines.flow.StateFlowKt;
/**
- * Keeps a list of all users on the device for user switching.
+ * Old implementation. Keeps a list of all users on the device for user switching.
+ *
+ * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
+ * instead.
*/
+@Deprecated
@SysUISingleton
-public class UserSwitcherController implements Dumpable {
-
- public static final float USER_SWITCH_ENABLED_ALPHA =
- LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA;
- public static final float USER_SWITCH_DISABLED_ALPHA =
- LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA;
+public class UserSwitcherControllerOldImpl implements UserSwitcherController {
private static final String TAG = "UserSwitcherController";
private static final boolean DEBUG = false;
@@ -123,7 +114,7 @@
private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l;
+ private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
@@ -132,7 +123,7 @@
protected final UserTracker mUserTracker;
protected final UserManager mUserManager;
private final ContentObserver mSettingsObserver;
- private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
+ private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
@VisibleForTesting
final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
@VisibleForTesting
@@ -158,7 +149,6 @@
@VisibleForTesting
Dialog mAddUserDialog;
private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
- private boolean mResumeUserOnGuestLogout = true;
private boolean mSimpleUserSwitcher;
// When false, there won't be any visual affordance to add a new user from the keyguard even if
// the user is unlocked
@@ -187,7 +177,8 @@
Collections.synchronizedList(new ArrayList<>());
@Inject
- public UserSwitcherController(Context context,
+ public UserSwitcherControllerOldImpl(
+ Context context,
IActivityManager activityManager,
UserManager userManager,
UserTracker userTracker,
@@ -303,16 +294,10 @@
refreshUsers(UserHandle.USER_NULL);
}
- /**
- * Refreshes users from UserManager.
- *
- * The pictures are only loaded if they have not been loaded yet.
- *
- * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
- */
+ @Override
@SuppressWarnings("unchecked")
- private void refreshUsers(int forcePictureLoadForId) {
- if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")");
+ public void refreshUsers(int forcePictureLoadForId) {
+ if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
if (forcePictureLoadForId != UserHandle.USER_NULL) {
mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
}
@@ -323,8 +308,8 @@
boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
- final int N = mUsers.size();
- for (int i = 0; i < N; i++) {
+ final int userCount = mUsers.size();
+ for (int i = 0; i < userCount; i++) {
UserRecord r = mUsers.get(i);
if (r == null || r.picture == null || r.info == null || forceAllUsers
|| mForcePictureLoadForUserId.get(r.info.id)) {
@@ -431,38 +416,41 @@
});
}
- boolean systemCanCreateUsers() {
+ private boolean systemCanCreateUsers() {
return !mUserManager.hasBaseUserRestriction(
UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
}
- boolean currentUserCanCreateUsers() {
+ private boolean currentUserCanCreateUsers() {
UserInfo currentUser = mUserTracker.getUserInfo();
return currentUser != null
&& (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
&& systemCanCreateUsers();
}
- boolean anyoneCanCreateUsers() {
+ private boolean anyoneCanCreateUsers() {
return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
}
+ @VisibleForTesting
boolean canCreateGuest(boolean hasExistingGuest) {
return mUserSwitcherEnabled
&& (currentUserCanCreateUsers() || anyoneCanCreateUsers())
&& !hasExistingGuest;
}
+ @VisibleForTesting
boolean canCreateUser() {
return mUserSwitcherEnabled
&& (currentUserCanCreateUsers() || anyoneCanCreateUsers())
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
}
- boolean createIsRestricted() {
+ private boolean createIsRestricted() {
return !mAddUsersFromLockScreen.getValue();
}
+ @VisibleForTesting
boolean canCreateSupervisedUser() {
return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
}
@@ -476,7 +464,7 @@
private void notifyAdapters() {
for (int i = mAdapters.size() - 1; i >= 0; i--) {
- BaseUserAdapter adapter = mAdapters.get(i).get();
+ BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
if (adapter != null) {
adapter.notifyDataSetChanged();
} else {
@@ -485,37 +473,20 @@
}
}
+ @Override
public boolean isSimpleUserSwitcher() {
return mSimpleUserSwitcher;
}
- public void setResumeUserOnGuestLogout(boolean resume) {
- mResumeUserOnGuestLogout = resume;
- }
-
/**
* Returns whether the current user is a system user.
*/
- public boolean isSystemUser() {
+ @VisibleForTesting
+ boolean isSystemUser() {
return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
}
- public void removeUserId(int userId) {
- if (userId == UserHandle.USER_SYSTEM) {
- Log.w(TAG, "User " + userId + " could not removed.");
- return;
- }
- if (mUserTracker.getUserId() == userId) {
- switchToUserId(UserHandle.USER_SYSTEM);
- }
- if (mUserManager.removeUser(userId)) {
- refreshUsers(UserHandle.USER_NULL);
- }
- }
-
- /**
- * @return UserRecord for the current user
- */
+ @Override
public @Nullable UserRecord getCurrentUserRecord() {
for (int i = 0; i < mUsers.size(); ++i) {
UserRecord userRecord = mUsers.get(i);
@@ -526,17 +497,7 @@
return null;
}
- /**
- * Notifies that a user has been selected.
- *
- * <p>This will trigger the right user journeys to create a guest user, switch users, and/or
- * navigate to the correct destination.
- *
- * <p>If a user with the given ID is not found, this method is a no-op.
- *
- * @param userId The ID of the user to switch to.
- * @param dialogShower An optional {@link DialogShower} in case we need to show dialogs.
- */
+ @Override
public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
UserRecord userRecord = mUsers.stream()
.filter(x -> x.resolveId() == userId)
@@ -549,23 +510,23 @@
onUserListItemClicked(userRecord, dialogShower);
}
- /** Whether it is allowed to add users while the device is locked. */
- public Flow<Boolean> getAddUsersFromLockScreen() {
+ @Override
+ public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
return mAddUsersFromLockScreen;
}
- /** Returns {@code true} if the guest user is configured to always be present on the device. */
+ @Override
public boolean isGuestUserAutoCreated() {
return mGuestUserAutoCreated;
}
- /** Returns {@code true} if the guest user is currently being reset. */
+ @Override
public boolean isGuestUserResetting() {
return mGuestIsResetting.get();
}
- @VisibleForTesting
- void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
+ @Override
+ public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
if (record.isGuest && record.info == null) {
createAndSwitchToGuestUser(dialogShower);
} else if (record.isAddUser) {
@@ -604,7 +565,7 @@
switchToUserId(id);
}
- protected void switchToUserId(int id) {
+ private void switchToUserId(int id) {
try {
if (mView != null) {
mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
@@ -621,7 +582,7 @@
private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
int newId = UserHandle.USER_SYSTEM;
- if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
+ if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
newId = info.id;
@@ -645,9 +606,7 @@
}
}
- /**
- * Creates and switches to the guest user.
- */
+ @Override
public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
createGuestAsync(guestId -> {
// guestId may be USER_NULL if we haven't reloaded the user list yet.
@@ -658,9 +617,7 @@
});
}
- /**
- * Shows the add user dialog.
- */
+ @Override
public void showAddUserDialog(@Nullable DialogShower dialogShower) {
if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
mAddUserDialog.cancel();
@@ -677,9 +634,7 @@
}
}
- /**
- * Starts an activity to add a supervised user to the device.
- */
+ @Override
public void startSupervisedUserActivity() {
final Intent intent = new Intent()
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
@@ -711,7 +666,7 @@
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
Log.v(TAG, "Broadcast: a=" + intent.getAction()
- + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
+ + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
}
boolean unpauseRefreshUsers = false;
@@ -725,8 +680,8 @@
final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
final UserInfo userInfo = mUserManager.getUserInfo(currentId);
- final int N = mUsers.size();
- for (int i = 0; i < N; i++) {
+ final int userCount = mUsers.size();
+ for (int i = 0; i < userCount; i++) {
UserRecord record = mUsers.get(i);
if (record.info == null) continue;
boolean shouldBeCurrent = record.info.id == currentId;
@@ -805,7 +760,7 @@
pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
}
- /** Returns the name of the current user of the phone. */
+ @Override
public String getCurrentUserName() {
if (mUsers.isEmpty()) return null;
UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
@@ -814,40 +769,22 @@
return item.info.name;
}
+ @Override
public void onDensityOrFontScaleChanged() {
refreshUsers(UserHandle.USER_ALL);
}
- @VisibleForTesting
- public void addAdapter(WeakReference<BaseUserAdapter> adapter) {
+ @Override
+ public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
mAdapters.add(adapter);
}
- @VisibleForTesting
+ @Override
public ArrayList<UserRecord> getUsers() {
return mUsers;
}
- /**
- * Removes guest user and switches to target user. The guest must be the current user and its id
- * must be {@code guestUserId}.
- *
- * <p>If {@code targetUserId} is {@link UserHandle#USER_NULL}, then create a new guest user in
- * the foreground, and immediately switch to it. This is used for wiping the current guest and
- * replacing it with a new one.
- *
- * <p>If {@code targetUserId} is specified, then remove the guest in the background while
- * switching to {@code targetUserId}.
- *
- * <p>If device is configured with {@link
- * com.android.internal.R.bool.config_guestUserAutoCreated}, then after guest user is removed, a
- * new one is created in the background. This has no effect if {@code targetUserId} is {@link
- * UserHandle#USER_NULL}.
- *
- * @param guestUserId id of the guest user to remove
- * @param targetUserId id of the user to switch to after guest is removed. If {@link
- * UserHandle#USER_NULL}, then switch immediately to the newly created guest user.
- */
+ @Override
public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
UserInfo currentUser = mUserTracker.getUserInfo();
if (currentUser.id != guestUserId) {
@@ -894,18 +831,9 @@
}
}
- /**
- * Exits guest user and switches to previous non-guest user. The guest must be the current
- * user.
- *
- * @param guestUserId user id of the guest user to exit
- * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when
- * target user id is not known
- * @param forceRemoveGuestOnExit true: remove guest before switching user,
- * false: remove guest only if its ephemeral, else keep guest
- */
+ @Override
public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
- boolean forceRemoveGuestOnExit) {
+ boolean forceRemoveGuestOnExit) {
UserInfo currentUser = mUserTracker.getUserInfo();
if (currentUser.id != guestUserId) {
Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
@@ -921,7 +849,7 @@
int newUserId = UserHandle.USER_SYSTEM;
if (targetUserId == UserHandle.USER_NULL) {
// when target user is not specified switch to last non guest user
- if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
+ if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
newUserId = info.id;
@@ -959,10 +887,7 @@
}
- /**
- * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
- * observer to wait until the device is provisioned, then schedule the guest creation.
- */
+ @Override
public void schedulePostBootGuestCreation() {
if (isDeviceAllowedToAddGuest()) {
guaranteeGuestPresent();
@@ -1014,7 +939,7 @@
* @return The multi-user user ID of the newly created guest user, or
* {@link UserHandle#USER_NULL} if the guest couldn't be created.
*/
- public @UserIdInt int createGuest() {
+ private @UserIdInt int createGuest() {
UserInfo guest;
try {
guest = mUserManager.createGuest(mContext);
@@ -1029,135 +954,27 @@
return guest.id;
}
- /**
- * Require a view for jank detection
- */
+ @Override
public void init(View view) {
mView = view;
}
- @VisibleForTesting
- public KeyguardStateController getKeyguardStateController() {
- return mKeyguardStateController;
+ @Override
+ public boolean isKeyguardShowing() {
+ return mKeyguardStateController.isShowing();
}
- /**
- * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one.
- */
+ @Override
@Nullable
public EnforcedAdmin getEnforcedAdmin(UserRecord record) {
return mEnforcedAdminByUserRecord.get(record);
}
- /**
- * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise.
- */
+ @Override
public boolean isDisabledByAdmin(UserRecord record) {
return mDisabledByAdmin.contains(record);
}
- public static abstract class BaseUserAdapter extends BaseAdapter {
-
- final UserSwitcherController mController;
- private final KeyguardStateController mKeyguardStateController;
-
- protected BaseUserAdapter(UserSwitcherController controller) {
- mController = controller;
- mKeyguardStateController = controller.getKeyguardStateController();
- controller.addAdapter(new WeakReference<>(this));
- }
-
- protected ArrayList<UserRecord> getUsers() {
- return mController.getUsers();
- }
-
- public int getUserCount() {
- return countUsers(false);
- }
-
- @Override
- public int getCount() {
- return countUsers(true);
- }
-
- private int countUsers(boolean includeGuest) {
- boolean keyguardShowing = mKeyguardStateController.isShowing();
- final int userSize = getUsers().size();
- int count = 0;
- for (int i = 0; i < userSize; i++) {
- if (getUsers().get(i).isGuest && !includeGuest) {
- continue;
- }
- if (getUsers().get(i).isRestricted && keyguardShowing) {
- break;
- }
- count++;
- }
- return count;
- }
-
- @Override
- public UserRecord getItem(int position) {
- return getUsers().get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- /**
- * It handles click events on user list items.
- *
- * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower}
- * will allow animation to and from the parent dialog.
- *
- */
- public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) {
- mController.onUserListItemClicked(record, dialogShower);
- }
-
- public void onUserListItemClicked(UserRecord record) {
- onUserListItemClicked(record, null);
- }
-
- public String getName(Context context, UserRecord item) {
- return getName(context, item, false);
- }
-
- /**
- * Returns the name for the given {@link UserRecord}.
- */
- public String getName(Context context, UserRecord item, boolean isTablet) {
- return LegacyUserUiHelper.getUserRecordName(
- context,
- item,
- mController.isGuestUserAutoCreated(),
- mController.isGuestUserResetting(),
- isTablet);
- }
-
- protected static ColorFilter getDisabledUserAvatarColorFilter() {
- ColorMatrix matrix = new ColorMatrix();
- matrix.setSaturation(0f); // 0 - grayscale
- return new ColorMatrixColorFilter(matrix);
- }
-
- protected static Drawable getIconDrawable(Context context, UserRecord item) {
- return getIconDrawable(context, item, false);
- }
- protected static Drawable getIconDrawable(Context context, UserRecord item,
- boolean isTablet) {
- int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
- item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet);
- return context.getDrawable(iconRes);
- }
-
- public void refresh() {
- mController.refreshUsers(UserHandle.USER_NULL);
- }
- }
-
private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId());
@@ -1178,20 +995,17 @@
defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
}
+ @Override
public void startActivity(Intent intent) {
mActivityStarter.startActivity(intent, true);
}
- /**
- * Add a subscriber to when user switches.
- */
+ @Override
public void addUserSwitchCallback(UserSwitchCallback callback) {
mUserSwitchCallbacks.add(callback);
}
- /**
- * Remove a subscriber to when user switches.
- */
+ @Override
public void removeUserSwitchCallback(UserSwitchCallback callback) {
mUserSwitchCallbacks.remove(callback);
}
@@ -1218,7 +1032,7 @@
// which
// helps making the transition faster.
if (!mKeyguardStateController.isShowing()) {
- mHandler.post(UserSwitcherController.this::notifyAdapters);
+ mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
} else {
notifyAdapters();
}
@@ -1367,13 +1181,4 @@
}
}
- /**
- * Callback to for when this controller receives the intent to switch users.
- */
- public interface UserSwitchCallback {
- /**
- * Called when user has switched.
- */
- void onUserSwitched();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 1b73539..b1b45b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,6 +58,8 @@
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.statusbar.policy.WalletControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -196,4 +198,8 @@
static DataSaverController provideDataSaverController(NetworkController networkController) {
return networkController.getDataSaverController();
}
+
+ /** Binds {@link UserSwitcherController} to its implementation. */
+ @Binds
+ UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 27746c0..00ed3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -36,6 +36,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -116,9 +117,12 @@
static BatteryController provideBatteryController(Context context,
EnhancedEstimates enhancedEstimates, PowerManager powerManager,
BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+ DumpManager dumpManager,
@Main Handler mainHandler, @Background Handler bgHandler) {
BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
- broadcastDispatcher, demoModeController, mainHandler, bgHandler);
+ broadcastDispatcher, demoModeController,
+ dumpManager,
+ mainHandler, bgHandler);
bC.init();
return bC;
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 5e2dde6..108ab43 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -53,10 +53,8 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter
import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter
-import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
-import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -66,10 +64,10 @@
private const val USER_VIEW = "user_view"
-/**
- * Support a fullscreen user switcher
- */
-open class UserSwitcherActivity @Inject constructor(
+/** Support a fullscreen user switcher */
+open class UserSwitcherActivity
+@Inject
+constructor(
private val userSwitcherController: UserSwitcherController,
private val broadcastDispatcher: BroadcastDispatcher,
private val falsingCollector: FalsingCollector,
@@ -86,11 +84,12 @@
private lateinit var addButton: View
private var addUserRecords = mutableListOf<UserRecord>()
private val onBackCallback = OnBackInvokedCallback { finish() }
- private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- finish()
+ private val userSwitchedCallback: UserTracker.Callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ finish()
+ }
}
- }
// When the add users options become available, insert another option to manage users
private val manageUserRecord =
UserRecord(
@@ -114,13 +113,14 @@
@VisibleForTesting
fun createActivity() {
setContentView(R.layout.user_switcher_fullscreen)
- window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
+ window.decorView.systemUiVisibility =
+ (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
if (isUsingModernArchitecture()) {
Log.d(TAG, "Using modern architecture.")
- val viewModel = ViewModelProvider(
- this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
+ val viewModel =
+ ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
UserSwitcherViewBinder.bind(
view = requireViewById(R.id.user_switcher_root),
viewModel = viewModel,
@@ -136,27 +136,23 @@
parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
- parent.touchHandler = object : Gefingerpoken {
- override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.onTouchEvent(ev)
- return false
+ parent.touchHandler =
+ object : Gefingerpoken {
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ falsingCollector.onTouchEvent(ev)
+ return false
+ }
}
- }
- requireViewById<View>(R.id.cancel).apply {
- setOnClickListener {
- _ -> finish()
- }
- }
+ requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } }
- addButton = requireViewById<View>(R.id.add).apply {
- setOnClickListener {
- _ -> showPopupMenu()
- }
- }
+ addButton =
+ requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } }
onBackInvokedDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback)
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ onBackCallback
+ )
userSwitcherController.init(parent)
initBroadcastReceiver()
@@ -169,25 +165,30 @@
val items = mutableListOf<UserRecord>()
addUserRecords.forEach { items.add(it) }
- var popupMenuAdapter = ItemAdapter(
- this,
- R.layout.user_switcher_fullscreen_popup_item,
- layoutInflater,
- { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
- { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply {
- setTint(resources.getColor(
- R.color.user_switcher_fullscreen_popup_item_tint,
- getTheme()
- ))
- } }
- )
+ var popupMenuAdapter =
+ ItemAdapter(
+ this,
+ R.layout.user_switcher_fullscreen_popup_item,
+ layoutInflater,
+ { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
+ { item: UserRecord ->
+ adapter.findUserIcon(item, true).mutate().apply {
+ setTint(
+ resources.getColor(
+ R.color.user_switcher_fullscreen_popup_item_tint,
+ getTheme()
+ )
+ )
+ }
+ }
+ )
popupMenuAdapter.addAll(items)
- popupMenu = UserSwitcherPopupMenu(this).apply {
- setAnchorView(addButton)
- setAdapter(popupMenuAdapter)
- setOnItemClickListener {
- parent: AdapterView<*>, view: View, pos: Int, id: Long ->
+ popupMenu =
+ UserSwitcherPopupMenu(this).apply {
+ setAnchorView(addButton)
+ setAdapter(popupMenuAdapter)
+ setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long ->
if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
return@setOnItemClickListener
}
@@ -206,10 +207,10 @@
if (!item.isAddUser) {
this@UserSwitcherActivity.finish()
}
- }
+ }
- show()
- }
+ show()
+ }
}
private fun buildUserViews() {
@@ -227,8 +228,8 @@
val totalWidth = parent.width
val userViewCount = adapter.getTotalUserViews()
val maxColumns = getMaxColumns(userViewCount)
- val horizontalGap = resources
- .getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
+ val horizontalGap =
+ resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap
val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns
@@ -299,14 +300,15 @@
}
private fun initBroadcastReceiver() {
- broadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.getAction()
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- finish()
+ broadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.getAction()
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ finish()
+ }
}
}
- }
val filter = IntentFilter()
filter.addAction(Intent.ACTION_SCREEN_OFF)
@@ -322,9 +324,7 @@
return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
}
- /**
- * Provides views to populate the option menu.
- */
+ /** Provides views to populate the option menu. */
private class ItemAdapter(
val parentContext: Context,
val resource: Int,
@@ -337,43 +337,27 @@
val item = getItem(position)
val view = convertView ?: layoutInflater.inflate(resource, parent, false)
- view.requireViewById<ImageView>(R.id.icon).apply {
- setImageDrawable(iconGetter(item))
- }
- view.requireViewById<TextView>(R.id.text).apply {
- setText(textGetter(item))
- }
+ view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) }
+ view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) }
return view
}
}
- private inner class UserAdapter : BaseUserAdapter(userSwitcherController) {
+ private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val item = getItem(position)
var view = convertView as ViewGroup?
if (view == null) {
- view = layoutInflater.inflate(
- R.layout.user_switcher_fullscreen_item,
- parent,
- false
- ) as ViewGroup
+ view =
+ layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false)
+ as ViewGroup
}
- (view.getChildAt(0) as ImageView).apply {
- setImageDrawable(getDrawable(item))
- }
- (view.getChildAt(1) as TextView).apply {
- setText(getName(getContext(), item))
- }
+ (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) }
+ (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) }
view.setEnabled(item.isSwitchToEnabled)
- view.setAlpha(
- if (view.isEnabled()) {
- USER_SWITCH_ENABLED_ALPHA
- } else {
- USER_SWITCH_DISABLED_ALPHA
- }
- )
+ UserSwitcherController.setSelectableAlpha(view)
view.setTag(USER_VIEW)
return view
}
@@ -401,23 +385,20 @@
}
fun getTotalUserViews(): Int {
- return users.count { item ->
- !doNotRenderUserView(item)
- }
+ return users.count { item -> !doNotRenderUserView(item) }
}
fun doNotRenderUserView(item: UserRecord): Boolean {
- return item.isAddUser ||
- item.isAddSupervisedUser ||
- item.isGuest && item.info == null
+ return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null
}
private fun getDrawable(item: UserRecord): Drawable {
- var drawable = if (item.isGuest) {
- getDrawable(R.drawable.ic_account_circle)
- } else {
- findUserIcon(item)
- }
+ var drawable =
+ if (item.isGuest) {
+ getDrawable(R.drawable.ic_account_circle)
+ } else {
+ findUserIcon(item)
+ }
drawable.mutate()
if (!item.isCurrent && !item.isSwitchToEnabled) {
@@ -429,16 +410,16 @@
)
}
- val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate()
- as LayerDrawable
- if (item == userSwitcherController.getCurrentUserRecord()) {
+ val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable
+ if (item == userSwitcherController.currentUserRecord) {
(ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
- val stroke = resources
- .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
- val color = Utils.getColorAttrDefaultColor(
- this@UserSwitcherActivity,
- com.android.internal.R.attr.colorAccentPrimary
- )
+ val stroke =
+ resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
+ val color =
+ Utils.getColorAttrDefaultColor(
+ this@UserSwitcherActivity,
+ com.android.internal.R.attr.colorAccentPrimary
+ )
setStroke(stroke, color)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 305b5ee..0356388 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -99,7 +99,7 @@
override val actions: Flow<List<UserActionModel>> =
userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
- override val isActionableWhenLocked: Flow<Boolean> = controller.addUsersFromLockScreen
+ override val isActionableWhenLocked: Flow<Boolean> = controller.isAddUsersFromLockScreenEnabled
override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index d7ad3ce..938417f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -174,6 +174,7 @@
setOnItemClickListener { _, _, position, _ ->
val itemPositionExcludingHeader = position - 1
adapter.getItem(itemPositionExcludingHeader).onClicked()
+ dismiss()
}
show()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 2714cf4..485a7e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -86,7 +86,6 @@
becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
- bouncerIsOrWillShow = false,
onlyFaceEnrolled = false,
faceAuthenticated = false,
faceDisabled = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 28e99da..43f6f1a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -116,9 +116,7 @@
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
- when(mUserSwitcherController.getKeyguardStateController())
- .thenReturn(mKeyguardStateController);
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
mScreenWidth = getUiDevice().getDisplayWidth();
mFakeMeasureSpec = View
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index ae980f5..409457e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1554,7 +1554,7 @@
anyBoolean());
CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue();
- bouncerWillBeVisibleSoon();
+ bouncerFullyVisible();
mTestableLooper.processAllMessages();
assertThat(cancelSignal.isCanceled()).isTrue();
@@ -1752,11 +1752,6 @@
setKeyguardBouncerVisibility(true);
}
- private void bouncerWillBeVisibleSoon() {
- mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, false);
- mTestableLooper.processAllMessages();
- }
-
private void setKeyguardBouncerVisibility(boolean isVisible) {
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index d70467d..c5a7de4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -36,6 +36,7 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
@@ -88,6 +89,9 @@
@Mock
ViewRootImpl mViewRoot;
+ @Mock
+ BouncerCallbackInteractor mBouncerCallbackInteractor;
+
DreamOverlayContainerViewController mController;
@Before
@@ -110,7 +114,8 @@
mResources,
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
- MILLIS_UNTIL_FULL_JITTER);
+ MILLIS_UNTIL_FULL_JITTER,
+ mBouncerCallbackInteractor);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 9d4275e..eec33ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -23,8 +23,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
+import android.os.RemoteException;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
@@ -57,6 +59,8 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamOverlayServiceTest extends SysuiTestCase {
+ private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
+ "lowlight");
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -129,7 +133,8 @@
mDreamOverlayComponentFactory,
mStateController,
mKeyguardUpdateMonitor,
- mUiEventLogger);
+ mUiEventLogger,
+ LOW_LIGHT_COMPONENT);
}
@Test
@@ -204,6 +209,22 @@
}
@Test
+ public void testLowLightSetByIntentExtra() throws RemoteException {
+ final Intent intent = new Intent();
+ intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT);
+
+ final IBinder proxy = mService.onBind(intent);
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ mMainExecutor.runAllReady();
+
+ verify(mStateController).setLowLightActive(true);
+ }
+
+ @Test
public void testDestroy() {
mService.onDestroy();
mMainExecutor.runAllReady();
@@ -211,6 +232,7 @@
verify(mKeyguardUpdateMonitor).removeCallback(any());
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
+ verify(mStateController).setLowLightActive(false);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 2adf285..d1d32a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -218,4 +218,20 @@
assertThat(stateController.getComplications(true).contains(complication))
.isTrue();
}
+
+ @Test
+ public void testNotifyLowLightChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.isLowLightActive()).isFalse();
+
+ stateController.setLowLightActive(true);
+
+ mExecutor.runAllReady();
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.isLowLightActive()).isTrue();
+ }
}
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 4ebae98..aa02178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -101,6 +102,8 @@
DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem;
@Mock
View mStatusBarItemView;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
private final Executor mMainExecutor = Runnable::run;
@@ -126,7 +129,8 @@
Optional.of(mDreamOverlayNotificationCountProvider),
mZenModeController,
mStatusBarWindowStateController,
- mDreamOverlayStatusBarItemsProvider);
+ mDreamOverlayStatusBarItemsProvider,
+ mDreamOverlayStateController);
}
@Test
@@ -137,6 +141,7 @@
verify(mZenModeController).addCallback(any());
verify(mDreamOverlayNotificationCountProvider).addCallback(any());
verify(mDreamOverlayStatusBarItemsProvider).addCallback(any());
+ verify(mDreamOverlayStateController).addCallback(any());
}
@Test
@@ -266,7 +271,8 @@
Optional.empty(),
mZenModeController,
mStatusBarWindowStateController,
- mDreamOverlayStatusBarItemsProvider);
+ mDreamOverlayStatusBarItemsProvider,
+ mDreamOverlayStateController);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
@@ -305,6 +311,7 @@
verify(mZenModeController).removeCallback(any());
verify(mDreamOverlayNotificationCountProvider).removeCallback(any());
verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any());
+ verify(mDreamOverlayStateController).removeCallback(any());
}
@Test
@@ -458,6 +465,7 @@
@Test
public void testStatusBarShownWhenSystemStatusBarHidden() {
mController.onViewAttached();
+ reset(mView);
final ArgumentCaptor<StatusBarWindowStateListener>
callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
@@ -471,6 +479,7 @@
public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
mController.onViewAttached();
mController.onViewDetached();
+ reset(mView);
final ArgumentCaptor<StatusBarWindowStateListener>
callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
@@ -493,4 +502,21 @@
verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView));
}
+
+ @Test
+ public void testLowLightHidesStatusBar() {
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
+ mController.onViewAttached();
+
+ verify(mView).setVisibility(View.INVISIBLE);
+ reset(mView);
+
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ verify(mView).setVisibility(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..3a61c57
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BouncerCallbackInteractorTest : SysuiTestCase() {
+ private val bouncerCallbackInteractor = BouncerCallbackInteractor()
+ @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback
+ @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback)
+ bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+ }
+
+ @Test
+ fun testOnFullyShown() {
+ bouncerCallbackInteractor.dispatchFullyShown()
+ verify(bouncerExpansionCallback).onFullyShown()
+ }
+
+ @Test
+ fun testOnFullyHidden() {
+ bouncerCallbackInteractor.dispatchFullyHidden()
+ verify(bouncerExpansionCallback).onFullyHidden()
+ }
+
+ @Test
+ fun testOnExpansionChanged() {
+ bouncerCallbackInteractor.dispatchExpansionChanged(5f)
+ verify(bouncerExpansionCallback).onExpansionChanged(5f)
+ }
+
+ @Test
+ fun testOnVisibilityChanged() {
+ bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+ verify(bouncerExpansionCallback).onVisibilityChanged(false)
+ }
+
+ @Test
+ fun testOnStartingToHide() {
+ bouncerCallbackInteractor.dispatchStartingToHide()
+ verify(bouncerExpansionCallback).onStartingToHide()
+ }
+
+ @Test
+ fun testOnStartingToShow() {
+ bouncerCallbackInteractor.dispatchStartingToShow()
+ verify(bouncerExpansionCallback).onStartingToShow()
+ }
+
+ @Test
+ fun testOnKeyguardReset() {
+ bouncerCallbackInteractor.dispatchReset()
+ verify(keyguardResetCallback).onKeyguardReset()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
new file mode 100644
index 0000000..e6c8dd8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class BouncerInteractorTest : SysuiTestCase() {
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private lateinit var repository: KeyguardBouncerRepository
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private lateinit var bouncerInteractor: BouncerInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ DejankUtils.setImmediate(true)
+ bouncerInteractor =
+ BouncerInteractor(
+ repository,
+ bouncerView,
+ mainHandler,
+ keyguardStateController,
+ keyguardSecurityModel,
+ bouncerCallbackInteractor,
+ falsingCollector,
+ dismissCallbackRegistry,
+ keyguardBypassController,
+ keyguardUpdateMonitor,
+ )
+ `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+ `when`(repository.show.value).thenReturn(null)
+ }
+
+ @Test
+ fun testShow_isScrimmed() {
+ bouncerInteractor.show(true)
+ verify(repository).setShowMessage(null)
+ verify(repository).setOnScreenTurnedOff(false)
+ verify(repository).setKeyguardAuthenticated(null)
+ verify(repository).setHide(false)
+ verify(repository).setStartingToHide(false)
+ verify(repository).setScrimmed(true)
+ verify(repository).setExpansion(EXPANSION_VISIBLE)
+ verify(repository).setShowingSoon(true)
+ verify(keyguardStateController).notifyBouncerShowing(true)
+ verify(bouncerCallbackInteractor).dispatchStartingToShow()
+ verify(repository).setVisible(true)
+ verify(repository).setShow(any(KeyguardBouncerModel::class.java))
+ verify(repository).setShowingSoon(false)
+ }
+
+ @Test
+ fun testShow_isNotScrimmed() {
+ verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+ }
+
+ @Test
+ fun testShow_keyguardIsDone() {
+ `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+ verify(keyguardStateController, never()).notifyBouncerShowing(true)
+ verify(bouncerCallbackInteractor, never()).dispatchStartingToShow()
+ }
+
+ @Test
+ fun testHide() {
+ bouncerInteractor.hide()
+ verify(falsingCollector).onBouncerHidden()
+ verify(keyguardStateController).notifyBouncerShowing(false)
+ verify(repository).setShowingSoon(false)
+ verify(repository).setOnDismissAction(null)
+ verify(repository).setVisible(false)
+ verify(repository).setHide(true)
+ verify(repository).setShow(null)
+ }
+
+ @Test
+ fun testExpansion() {
+ `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ bouncerInteractor.setExpansion(0.6f)
+ verify(repository).setExpansion(0.6f)
+ verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+ }
+
+ @Test
+ fun testExpansion_fullyShown() {
+ `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+ bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+ verify(falsingCollector).onBouncerShown()
+ verify(bouncerCallbackInteractor).dispatchFullyShown()
+ }
+
+ @Test
+ fun testExpansion_fullyHidden() {
+ `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+ bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+ verify(repository).setVisible(false)
+ verify(repository).setShow(null)
+ verify(falsingCollector).onBouncerHidden()
+ verify(bouncerCallbackInteractor).dispatchReset()
+ verify(bouncerCallbackInteractor).dispatchFullyHidden()
+ }
+
+ @Test
+ fun testExpansion_startingToHide() {
+ `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ bouncerInteractor.setExpansion(0.1f)
+ verify(repository).setStartingToHide(true)
+ verify(bouncerCallbackInteractor).dispatchStartingToHide()
+ }
+
+ @Test
+ fun testShowMessage() {
+ bouncerInteractor.showMessage("abc", null)
+ verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
+ }
+
+ @Test
+ fun testDismissAction() {
+ val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+ val cancelAction = mock(Runnable::class.java)
+ bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+ verify(repository)
+ .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ }
+
+ @Test
+ fun testUpdateResources() {
+ bouncerInteractor.updateResources()
+ verify(repository).setResourceUpdateRequests(true)
+ }
+
+ @Test
+ fun testNotifyKeyguardAuthenticated() {
+ bouncerInteractor.notifyKeyguardAuthenticated(true)
+ verify(repository).setKeyguardAuthenticated(true)
+ }
+
+ @Test
+ fun testOnScreenTurnedOff() {
+ bouncerInteractor.onScreenTurnedOff()
+ verify(repository).setOnScreenTurnedOff(true)
+ }
+
+ @Test
+ fun testSetKeyguardPosition() {
+ bouncerInteractor.setKeyguardPosition(0f)
+ verify(repository).setKeyguardPosition(0f)
+ }
+
+ @Test
+ fun testNotifyKeyguardAuthenticatedHandled() {
+ bouncerInteractor.notifyKeyguardAuthenticatedHandled()
+ verify(repository).setKeyguardAuthenticated(null)
+ }
+
+ @Test
+ fun testNotifyUpdatedResources() {
+ bouncerInteractor.notifyUpdatedResources()
+ verify(repository).setResourceUpdateRequests(false)
+ }
+
+ @Test
+ fun testSetBackButtonEnabled() {
+ bouncerInteractor.setBackButtonEnabled(true)
+ verify(repository).setIsBackButtonEnabled(true)
+ }
+
+ @Test
+ fun testStartDisappearAnimation() {
+ val runnable = mock(Runnable::class.java)
+ bouncerInteractor.startDisappearAnimation(runnable)
+ verify(repository).setStartDisappearAnimation(any(Runnable::class.java))
+ }
+
+ @Test
+ fun testIsFullShowing() {
+ `when`(repository.isVisible.value).thenReturn(true)
+ `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+ assertThat(bouncerInteractor.isFullyShowing()).isTrue()
+ `when`(repository.isVisible.value).thenReturn(false)
+ assertThat(bouncerInteractor.isFullyShowing()).isFalse()
+ }
+
+ @Test
+ fun testIsScrimmed() {
+ `when`(repository.isScrimmed.value).thenReturn(true)
+ assertThat(bouncerInteractor.isScrimmed()).isTrue()
+ `when`(repository.isScrimmed.value).thenReturn(false)
+ assertThat(bouncerInteractor.isScrimmed()).isFalse()
+ }
+
+ @Test
+ fun testIsInTransit() {
+ `when`(repository.showingSoon.value).thenReturn(true)
+ assertThat(bouncerInteractor.isInTransit()).isTrue()
+ `when`(repository.showingSoon.value).thenReturn(false)
+ assertThat(bouncerInteractor.isInTransit()).isFalse()
+ `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ assertThat(bouncerInteractor.isInTransit()).isTrue()
+ }
+
+ @Test
+ fun testIsAnimatingAway() {
+ `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {})
+ assertThat(bouncerInteractor.isAnimatingAway()).isTrue()
+ `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+ assertThat(bouncerInteractor.isAnimatingAway()).isFalse()
+ }
+
+ @Test
+ fun testWillDismissWithAction() {
+ `when`(repository.onDismissAction.value?.onDismissAction)
+ .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+ assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
+ `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+ assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
+ }
+}
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 22ecb4b..5f64336 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
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -102,6 +104,18 @@
}
@Test
+ public void getItemId_validPosition_returnCorrespondingId() {
+ assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaDevices.get(
+ 0).getId().hashCode());
+ }
+
+ @Test
+ public void getItemId_invalidPosition_returnPosition() {
+ int invalidPosition = mMediaDevices.size() + 1;
+ assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
+ }
+
+ @Test
public void onBindViewHolder_bindPairNew_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
@@ -155,6 +169,33 @@
}
@Test
+ public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
+ when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ }
+
+ @Test
+ public void onBindViewHolder_isMutingExpectedDevice_verifyView() {
+ when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ }
+
+ @Test
public void onBindViewHolder_initSeekbar_setsVolume() {
when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_CURRENT_VOLUME);
@@ -165,6 +206,20 @@
}
@Test
+ public void onBindViewHolder_bindSelectableDevice_verifyView() {
+ List<MediaDevice> selectableDevices = new ArrayList<>();
+ selectableDevices.add(mMediaDevice2);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ }
+
+ @Test
public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
@@ -223,6 +278,22 @@
}
@Test
+ public void onBindViewHolder_bindGroupingDevice_verifyView() {
+ when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaDevice1.getState()).thenReturn(
+ LocalMediaManager.MediaDeviceState.STATE_GROUPING);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() {
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice2.getState()).thenReturn(
@@ -256,6 +327,31 @@
}
@Test
+ public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() {
+ when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaOutputController).cancelMuteAwaitConnection();
+ }
+
+ @Test
+ public void onItemClick_clicksSelectableDevice_triggerGrouping() {
+ List<MediaDevice> selectableDevices = new ArrayList<>();
+ selectableDevices.add(mMediaDevice2);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
+ }
+
+ @Test
public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
List<MediaDevice> selectableDevices = new ArrayList<>();
@@ -280,4 +376,14 @@
assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
}
+
+ @Test
+ public void updateColorScheme_triggerController() {
+ WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888));
+
+ mMediaOutputAdapter.updateColorScheme(wallpaperColors, true);
+
+ verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index eb8ecae..9be201e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -241,46 +241,29 @@
}
@Test
- public void onStart_isBroadcasting_verifyRegisterLeBroadcastServiceCallBack() {
+ public void whenBroadcasting_verifyLeBroadcastServiceCallBackIsRegisteredAndUnregistered() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
mIsBroadcasting = true;
mMediaOutputBaseDialogImpl.onStart();
-
verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
- }
-
- @Test
- public void onStart_notBroadcasting_noRegisterLeBroadcastServiceCallBack() {
- when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
- mLocalBluetoothLeBroadcast);
- mIsBroadcasting = false;
-
- mMediaOutputBaseDialogImpl.onStart();
-
- verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any());
- }
-
- @Test
- public void onStart_isBroadcasting_verifyUnregisterLeBroadcastServiceCallBack() {
- when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
- mLocalBluetoothLeBroadcast);
- mIsBroadcasting = true;
mMediaOutputBaseDialogImpl.onStop();
-
verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
}
@Test
- public void onStop_notBroadcasting_noUnregisterLeBroadcastServiceCallBack() {
+ public void
+ whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsNotRegisteredOrUnregistered() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
mIsBroadcasting = false;
+ mMediaOutputBaseDialogImpl.onStart();
mMediaOutputBaseDialogImpl.onStop();
+ verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any());
verify(mLocalBluetoothLeBroadcast, never()).unregisterServiceCallBack(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 6dcf802..cb31fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -35,6 +35,7 @@
import android.app.Notification;
import android.content.Context;
import android.graphics.drawable.Icon;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -279,6 +280,203 @@
}
@Test
+ public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+ mMediaOutputController.mIsRefreshing = true;
+
+ mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ }
+
+ @Test
+ public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
+ when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+
+ mMediaOutputController.cancelMuteAwaitConnection();
+
+ verify(mAudioManager).cancelMuteAwaitConnection(any());
+ }
+
+ @Test
+ public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() {
+ when(mAudioManager.getMutingExpectedDevice()).thenReturn(null);
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+ mMediaOutputController.cancelMuteAwaitConnection();
+
+ verify(mAudioManager, never()).cancelMuteAwaitConnection(any());
+ }
+
+ @Test
+ public void getAppSourceName_packageNameIsNull_returnsNull() {
+ MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+ "",
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
+ testMediaOutputController.start(mCb);
+ reset(mCb);
+
+ testMediaOutputController.getAppSourceName();
+
+ assertThat(testMediaOutputController.getAppSourceName()).isNull();
+ }
+
+ @Test
+ public void isActiveItem_deviceNotConnected_returnsFalse() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
+
+ assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse();
+ }
+
+ @Test
+ public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
+ MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+ "",
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
+ testMediaOutputController.start(mCb);
+ reset(mCb);
+
+ testMediaOutputController.getAppSourceName();
+
+ assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull();
+ }
+
+ @Test
+ public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() {
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+ mMediaOutputController.mNeedRefresh = true;
+
+ mMediaOutputController.refreshDataSetIfNeeded();
+
+ assertThat(mMediaOutputController.mNeedRefresh).isFalse();
+ }
+
+ @Test
+ public void isCurrentConnectedDeviceRemote_containsFeatures_returnsTrue() {
+ when(mMediaDevice1.getFeatures()).thenReturn(
+ ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+
+ assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue();
+ }
+
+ @Test
+ public void addDeviceToPlayMedia_triggersFromLocalMediaManager() {
+ MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+ null,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
+
+ LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
+ testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
+
+ testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2);
+
+ verify(testLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2);
+ }
+
+ @Test
+ public void removeDeviceFromPlayMedia_triggersFromLocalMediaManager() {
+ MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+ null,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
+
+ LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
+ testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
+
+ testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2);
+
+ verify(testLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2);
+ }
+
+ @Test
+ public void getDeselectableMediaDevice_triggersFromLocalMediaManager() {
+ mMediaOutputController.getDeselectableMediaDevice();
+
+ verify(mLocalMediaManager).getDeselectableMediaDevice();
+ }
+
+ @Test
+ public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() {
+ int testVolume = 10;
+ mMediaOutputController.adjustSessionVolume(testVolume);
+
+ verify(mLocalMediaManager).adjustSessionVolume(testVolume);
+ }
+
+ @Test
+ public void getSessionVolumeMax_triggersFromLocalMediaManager() {
+ mMediaOutputController.getSessionVolumeMax();
+
+ verify(mLocalMediaManager).getSessionVolumeMax();
+ }
+
+ @Test
+ public void getSessionVolume_triggersFromLocalMediaManager() {
+ mMediaOutputController.getSessionVolume();
+
+ verify(mLocalMediaManager).getSessionVolume();
+ }
+
+ @Test
+ public void getSessionName_triggersFromLocalMediaManager() {
+ mMediaOutputController.getSessionName();
+
+ verify(mLocalMediaManager).getSessionName();
+ }
+
+ @Test
+ public void releaseSession_triggersFromLocalMediaManager() {
+ mMediaOutputController.releaseSession();
+
+ verify(mLocalMediaManager).releaseSession();
+ }
+
+ @Test
+ public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() {
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+
+ mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse();
+ }
+
+ @Test
+ public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() {
+ when(mMediaDevice1.getState()).thenReturn(
+ LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+
+ mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue();
+ }
+
+ @Test
+ public void isPlaying_stateIsNull() {
+ when(mMediaController.getPlaybackState()).thenReturn(null);
+
+ assertThat(mMediaOutputController.isPlaying()).isFalse();
+ }
+
+ @Test
public void onSelectedDeviceStateChanged_verifyCallback() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
mMediaOutputController.start(mCb);
@@ -535,6 +733,44 @@
}
@Test
+ public void getNotificationSmallIcon_withoutSmallIcon_returnsNull() {
+ final List<NotificationEntry> entryList = new ArrayList<>();
+ final NotificationEntry entry = mock(NotificationEntry.class);
+ final StatusBarNotification sbn = mock(StatusBarNotification.class);
+ final Notification notification = mock(Notification.class);
+ entryList.add(entry);
+
+ when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
+ when(entry.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(notification);
+ when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(notification.isMediaNotification()).thenReturn(true);
+ when(notification.getSmallIcon()).thenReturn(null);
+
+ assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull();
+ }
+
+ @Test
+ public void getNotificationSmallIcon_withPackageNameAndMediaSession_returnsIconCompat() {
+ final List<NotificationEntry> entryList = new ArrayList<>();
+ final NotificationEntry entry = mock(NotificationEntry.class);
+ final StatusBarNotification sbn = mock(StatusBarNotification.class);
+ final Notification notification = mock(Notification.class);
+ final Icon icon = mock(Icon.class);
+ entryList.add(entry);
+
+ when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
+ when(entry.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(notification);
+ when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(notification.isMediaNotification()).thenReturn(true);
+ when(notification.getSmallIcon()).thenReturn(icon);
+
+ assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf(
+ IconCompat.class);
+ }
+
+ @Test
public void isVolumeControlEnabled_isCastWithVolumeFixed_returnsFalse() {
when(mMediaDevice1.getDeviceType()).thenReturn(
MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 9557513..bae3569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -25,7 +25,10 @@
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
+import android.graphics.Bitmap;
import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
import android.media.MediaRoute2Info;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -43,6 +46,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -82,6 +86,8 @@
private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
+ private final MediaDescription mMediaDescription = mock(MediaDescription.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
@@ -100,6 +106,8 @@
when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_NONE);
when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE);
+ when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
+ when(mMediaMetadata.getDescription()).thenReturn(mMediaDescription);
mMediaControllers.add(mMediaController);
when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
@@ -207,6 +215,80 @@
}
@Test
+ public void getHeaderIcon_getFromMediaControllerMetaData() {
+ int testWidth = 10;
+ int testHeight = 20;
+ when(mMediaDescription.getIconBitmap())
+ .thenReturn(Bitmap.createBitmap(testWidth, testHeight, Bitmap.Config.ARGB_8888));
+
+ assertThat(mMediaOutputDialog.getHeaderIcon().getBitmap().getHeight()).isEqualTo(
+ testHeight);
+ assertThat(mMediaOutputDialog.getHeaderIcon().getBitmap().getWidth()).isEqualTo(testWidth);
+ }
+
+ @Test
+ public void getHeaderText_getFromMediaControllerMetaData() {
+ String testTitle = "test title";
+ when(mMediaDescription.getTitle())
+ .thenReturn(testTitle);
+ assertThat(mMediaOutputDialog.getHeaderText().toString()).isEqualTo(testTitle);
+ }
+
+ @Test
+ public void getHeaderSubtitle_getFromMediaControllerMetaData() {
+ String testSubtitle = "test title";
+ when(mMediaDescription.getSubtitle())
+ .thenReturn(testSubtitle);
+
+ assertThat(mMediaOutputDialog.getHeaderSubtitle().toString()).isEqualTo(testSubtitle);
+ }
+
+ @Test
+ public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() {
+ String stopText = mContext.getText(R.string.keyboard_key_media_stop).toString();
+ MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
+ when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
+
+ MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
+ mockMediaOutputController, mUiEventLogger);
+ testDialog.show();
+
+ assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+ }
+
+ @Test
+ public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
+ String stopText = mContext.getText(R.string.media_output_broadcast).toString();
+ MediaDevice mMediaDevice = mock(MediaDevice.class);
+ MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
+ when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true);
+ when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice);
+ when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true);
+ when(mockMediaOutputController.isPlaying()).thenReturn(true);
+ when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
+ MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
+ mockMediaOutputController, mUiEventLogger);
+ testDialog.show();
+
+ assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+ }
+
+ @Test
+ public void onStopButtonClick_notPlaying_releaseSession() {
+ MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
+ when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
+ when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null);
+ when(mockMediaOutputController.isPlaying()).thenReturn(false);
+ MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
+ mockMediaOutputController, mUiEventLogger);
+ testDialog.show();
+
+ testDialog.onStopButtonClick();
+
+ verify(mockMediaOutputController).releaseSession();
+ }
+
+ @Test
// Check the visibility metric logging by creating a new MediaOutput dialog,
// and verify if the calling times increases.
public void onCreate_ShouldLogVisibility() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index ecc8457..cbe1186 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -226,7 +226,9 @@
+ " Tile records:\n"
+ " " + mockTileString + "\n"
+ " " + mockTileViewString + "\n"
- + " media bounds: null\n";
+ + " media bounds: null\n"
+ + " horizontal layout: false\n"
+ + " last orientation: 0\n";
assertEquals(expected, w.getBuffer().toString());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 39f27d4..4af5b90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -123,8 +123,7 @@
@Test
fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
- // times(2) because both controller and base controller are registering their listeners
- verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+ verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
// verify that media starts in the expanded state by default
verify(mediaHost).expansion = MediaHostState.EXPANDED
@@ -139,8 +138,7 @@
@Test
fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
- // times(2) because both controller and base controller are registering their listeners
- verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+ verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
reset(mediaHost)
usingCollapsedLandscapeMedia = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2adc389..481e4e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,12 +21,16 @@
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardHostViewController
import com.android.keyguard.LockIconViewController
+import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -51,9 +55,9 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
-@SmallTest
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var view: NotificationShadeWindowView
@@ -72,8 +76,12 @@
@Mock
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
private lateinit var ambientState: AmbientState
@Mock
+ private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
+ @Mock
private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock
private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -87,6 +95,10 @@
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
+ @Mock lateinit var keyguardBouncerContainer: ViewGroup
+ @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
+ @Mock lateinit var keyguardHostViewController: KeyguardHostViewController
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -97,7 +109,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(view.bottom).thenReturn(VIEW_BOTTOM)
-
underTest = NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
FalsingCollectorFake(),
@@ -115,7 +126,10 @@
notificationShadeWindowController,
keyguardUnlockAnimationController,
ambientState,
- pulsingGestureListener
+ pulsingGestureListener,
+ featureFlags,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 001bfee..4a7dec9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -33,11 +33,14 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -86,6 +89,9 @@
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private AmbientState mAmbientState;
@Mock private PulsingGestureListener mPulsingGestureListener;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
+ @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -121,7 +127,10 @@
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
mAmbientState,
- mPulsingGestureListener
+ mPulsingGestureListener,
+ mFeatureFlags,
+ mKeyguardBouncerViewModel,
+ mKeyguardBouncerComponentFactory
);
mController.setupExpandedStatusBar();
mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 945cf7f..9a13e93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -635,6 +635,19 @@
}
@Test
+ public void transientIndication_visibleWhenDozing_ignoresPowerPressed() {
+ createController();
+
+ mController.setVisible(true);
+ reset(mRotateTextViewController);
+ mController.getKeyguardCallback().onBiometricError(
+ FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "foo",
+ BiometricSourceType.FINGERPRINT);
+
+ verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ }
+
+ @Test
public void transientIndication_swipeUpToRetry() {
createController();
String message = mContext.getString(R.string.keyguard_retry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index a4453f8..ee4b9d9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -41,11 +41,17 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -101,6 +107,13 @@
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@Mock private DreamOverlayStateController mDreamOverlayStateController;
@Mock private LatencyTracker mLatencyTracker;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
+ @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
+ @Mock private BouncerInteractor mBouncerInteractor;
+ @Mock private BouncerView mBouncerView;
+// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference;
+ @Mock private BouncerViewDelegate mBouncerViewDelegate;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
@@ -115,6 +128,8 @@
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
.thenReturn(mKeyguardMessageAreaController);
+ when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
getContext(),
@@ -133,7 +148,12 @@
mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
- mLatencyTracker);
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mBouncerCallbackInteractor,
+ mBouncerInteractor,
+ mBouncerView);
mStatusBarKeyguardViewManager.registerCentralSurfaces(
mCentralSurfaces,
mNotificationPanelView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
index 37c0f36..bf43238 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
@@ -34,14 +34,14 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@SmallTest
-class StatusBarUserSwitcherControllerTest : SysuiTestCase() {
+class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
@Mock
private lateinit var tracker: StatusBarUserInfoTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
new file mode 100644
index 0000000..f304647
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.policy
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserHandle
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.lang.ref.WeakReference
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BaseUserSwitcherAdapterTest : SysuiTestCase() {
+
+ @Mock private lateinit var controller: UserSwitcherController
+
+ private lateinit var underTest: BaseUserSwitcherAdapter
+
+ private lateinit var users: ArrayList<UserRecord>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ users =
+ ArrayList(
+ listOf(
+ createUserRecord(
+ id = 0,
+ picture = mock(),
+ isSelected = true,
+ isGuest = false,
+ ),
+ createUserRecord(
+ id = 1,
+ picture = mock(),
+ isSelected = false,
+ isGuest = false,
+ ),
+ createUserRecord(
+ id = UserHandle.USER_NULL,
+ picture = null,
+ isSelected = false,
+ isGuest = true,
+ ),
+ )
+ )
+
+ whenever(controller.users).thenAnswer { users }
+
+ underTest =
+ object : BaseUserSwitcherAdapter(controller) {
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
+ return mock()
+ }
+ }
+ }
+
+ @Test
+ fun `Adds self to controller in constructor`() {
+ val captor = kotlinArgumentCaptor<WeakReference<BaseUserSwitcherAdapter>>()
+ verify(controller).addAdapter(captor.capture())
+
+ assertThat(captor.value.get()).isEqualTo(underTest)
+ }
+
+ @Test
+ fun count() {
+ assertThat(underTest.count).isEqualTo(users.size)
+ }
+
+ @Test
+ fun `count - ignores restricted users when device is locked`() {
+ whenever(controller.isKeyguardShowing).thenReturn(true)
+ users =
+ ArrayList(
+ listOf(
+ createUserRecord(
+ id = 0,
+ picture = mock(),
+ isSelected = true,
+ isGuest = false,
+ isRestricted = false,
+ ),
+ createUserRecord(
+ id = 1,
+ picture = mock(),
+ isSelected = false,
+ isGuest = false,
+ isRestricted = true, // this one will be ignored.
+ ),
+ createUserRecord(
+ id = UserHandle.USER_NULL,
+ picture = null,
+ isSelected = false,
+ isGuest = true,
+ ),
+ )
+ )
+ assertThat(underTest.count).isEqualTo(users.size - 1)
+ }
+
+ @Test
+ fun `count - does not ignore restricted users when device is not locked`() {
+ whenever(controller.isKeyguardShowing).thenReturn(false)
+ users =
+ ArrayList(
+ listOf(
+ createUserRecord(
+ id = 0,
+ picture = mock(),
+ isSelected = true,
+ isGuest = false,
+ isRestricted = false,
+ ),
+ createUserRecord(
+ id = 1,
+ picture = mock(),
+ isSelected = false,
+ isGuest = false,
+ isRestricted = true,
+ ),
+ createUserRecord(
+ id = UserHandle.USER_NULL,
+ picture = null,
+ isSelected = false,
+ isGuest = true,
+ ),
+ )
+ )
+ assertThat(underTest.count).isEqualTo(users.size)
+ }
+
+ @Test
+ fun getItem() {
+ assertThat((0 until underTest.count).map { position -> underTest.getItem(position) })
+ .isEqualTo(users)
+ }
+
+ @Test
+ fun getItemId() {
+ (0 until underTest.count).map { position ->
+ assertThat(underTest.getItemId(position)).isEqualTo(position)
+ }
+ }
+
+ @Test
+ fun onUserListItemClicked() {
+ val userRecord = users[users.size / 2]
+ val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+ underTest.onUserListItemClicked(userRecord, dialogShower)
+
+ verify(controller).onUserListItemClicked(userRecord, dialogShower)
+ }
+
+ @Test
+ fun `getName - non guest - returns real name`() {
+ val userRecord =
+ createUserRecord(
+ id = 1,
+ picture = mock(),
+ )
+
+ assertThat(underTest.getName(context, userRecord)).isEqualTo(userRecord.info?.name)
+ }
+
+ @Test
+ fun `getName - guest and selected - returns exit guest action name`() {
+ val expected = "Exit guest"
+ context.orCreateTestableResources.addOverride(
+ com.android.settingslib.R.string.guest_exit_quick_settings_button,
+ expected,
+ )
+
+ val userRecord =
+ createUserRecord(
+ id = 2,
+ picture = null,
+ isGuest = true,
+ isSelected = true,
+ )
+
+ assertThat(underTest.getName(context, userRecord)).isEqualTo(expected)
+ }
+
+ @Test
+ fun `getName - guest and not selected - returns enter guest action name`() {
+ val expected = "Guest"
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.string.guest_name,
+ expected,
+ )
+
+ val userRecord =
+ createUserRecord(
+ id = 2,
+ picture = null,
+ isGuest = true,
+ isSelected = false,
+ )
+
+ assertThat(underTest.getName(context, userRecord)).isEqualTo("Guest")
+ }
+
+ @Test
+ fun refresh() {
+ underTest.refresh()
+
+ verify(controller).refreshUsers(UserHandle.USER_NULL)
+ }
+
+ private fun createUserRecord(
+ id: Int,
+ picture: Bitmap? = null,
+ isSelected: Boolean = false,
+ isGuest: Boolean = false,
+ isAction: Boolean = false,
+ isRestricted: Boolean = false,
+ ): UserRecord {
+ return UserRecord(
+ info =
+ if (isAction) {
+ null
+ } else {
+ UserInfo(id, "name$id", 0)
+ },
+ picture = picture,
+ isCurrent = isSelected,
+ isGuest = isGuest,
+ isRestricted = isRestricted,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index fda80a2..43d0fe9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -80,6 +81,7 @@
mPowerManager,
mBroadcastDispatcher,
mDemoModeController,
+ mock(DumpManager.class),
new Handler(),
new Handler());
// Can throw if updateEstimate is called on the main thread
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index b4f3987b..b86ca6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -38,9 +38,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -102,8 +102,7 @@
ViewUtils.attachView(view)
testableLooper.processAllMessages()
- `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController)
- `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true)
+ `when`(userSwitcherController.isKeyguardShowing).thenReturn(true)
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
keyguardQsUserSwitchController.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
index 8dcd4bb..76ecc1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
@@ -86,7 +86,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
-class UserSwitcherControllerTest : SysuiTestCase() {
+class UserSwitcherControllerOldImplTest : SysuiTestCase() {
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@@ -118,7 +118,7 @@
private lateinit var longRunningExecutor: FakeExecutor
private lateinit var uiExecutor: FakeExecutor
private lateinit var uiEventLogger: UiEventLoggerFake
- private lateinit var userSwitcherController: UserSwitcherController
+ private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
private lateinit var picture: Bitmap
private val ownerId = UserHandle.USER_SYSTEM
private val ownerInfo = UserInfo(ownerId, "Owner", null,
@@ -205,7 +205,8 @@
}
private fun setupController() {
- userSwitcherController = UserSwitcherController(
+ userSwitcherController =
+ UserSwitcherControllerOldImpl(
mContext,
activityManager,
userManager,
@@ -230,7 +231,8 @@
dumpManager,
dialogLaunchAnimator,
guestResumeSessionReceiver,
- guestResetOrExitSessionReceiver)
+ guestResetOrExitSessionReceiver
+ )
userSwitcherController.init(notificationShadeWindowView)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 6b466e1..6fec343 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -60,7 +60,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(controller.addUsersFromLockScreen).thenReturn(MutableStateFlow(false))
+ whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
whenever(controller.isGuestUserAutoCreated).thenReturn(false)
whenever(controller.isGuestUserResetting).thenReturn(false)
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index b5fdaca..6bb19ce 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -32,6 +32,8 @@
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
@@ -72,19 +74,32 @@
NOT_SHOWN_REASON_VIEW_CHANGED,
NOT_SHOWN_REASON_ACTIVITY_FINISHED,
NOT_SHOWN_REASON_REQUEST_TIMEOUT,
+ NOT_SHOWN_REASON_REQUEST_FAILED,
+ NOT_SHOWN_REASON_NO_FOCUS,
NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY,
NOT_SHOWN_REASON_UNKNOWN
})
@Retention(RetentionPolicy.SOURCE)
public @interface NotShownReason {}
- public static final int NOT_SHOWN_REASON_ANY_SHOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
- public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
- public static final int NOT_SHOWN_REASON_VIEW_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
- public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
- public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT;
- public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
- public static final int NOT_SHOWN_REASON_UNKNOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
+ public static final int NOT_SHOWN_REASON_ANY_SHOWN =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
+ public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
+ public static final int NOT_SHOWN_REASON_VIEW_CHANGED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
+ public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
+ public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT;
+ public static final int NOT_SHOWN_REASON_REQUEST_FAILED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED;
+ public static final int NOT_SHOWN_REASON_NO_FOCUS =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS;
+ public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
+ public static final int NOT_SHOWN_REASON_UNKNOWN =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
private final int mSessionId;
private Optional<PresentationStatsEventInternal> mEventInternal;
@@ -118,6 +133,14 @@
});
}
+ public void maybeSetNoPresentationEventReasonIfNoReasonExists(@NotShownReason int reason) {
+ mEventInternal.ifPresent(event -> {
+ if (event.mCountShown == 0 && event.mNoPresentationReason == NOT_SHOWN_REASON_UNKNOWN) {
+ event.mNoPresentationReason = reason;
+ }
+ });
+ }
+
public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList,
AutofillId currentViewId) {
mEventInternal.ifPresent(event -> {
@@ -180,7 +203,8 @@
public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) {
mEventInternal.ifPresent(event -> {
- event.mDisplayPresentationType = UI_TYPE_INLINE;
+ event.mDisplayPresentationType =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, userId);
if (TextUtils.isEmpty(imeString)) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3d955b7..5c11e2c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -43,6 +43,8 @@
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import static com.android.server.autofill.Helper.toArray;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_CHANGED;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED;
@@ -402,6 +404,13 @@
@GuardedBy("mLock")
private PresentationStatsEventLogger mPresentationStatsEventLogger;
+ /**
+ * Fill dialog request would likely be sent slightly later.
+ */
+ @NonNull
+ @GuardedBy("mLock")
+ private boolean mStartedLogEventWithoutFocus;
+
void onSwitchInputMethodLocked() {
// One caveat is that for the case where the focus is on a field for which regular autofill
// returns null, and augmented autofill is triggered, and then the user switches the input
@@ -522,6 +531,7 @@
}
mLastFillRequest = mPendingFillRequest;
+ mPresentationStatsEventLogger.maybeSetIsNewRequest(true);
mRemoteFillService.onFillRequest(mPendingFillRequest);
mPendingInlineSuggestionsRequest = null;
mWaitForInlineRequest = false;
@@ -1128,6 +1138,7 @@
@Override
public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags) {
+
final AutofillId[] fieldClassificationIds;
final LogMaker requestLog;
@@ -1285,9 +1296,8 @@
}
}
- // TODO(b/234185326): Add separate reason for failures.
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
- NOT_SHOWN_REASON_REQUEST_TIMEOUT);
+ timedOut ? NOT_SHOWN_REASON_REQUEST_TIMEOUT : NOT_SHOWN_REASON_REQUEST_FAILED);
mPresentationStatsEventLogger.logAndEndEvent();
}
notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED,
@@ -2816,6 +2826,7 @@
@GuardedBy("mLock")
private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
@NonNull ViewState viewState, int flags) {
+ // Force new response for manual request
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mSessionFlags.mAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
@@ -2823,7 +2834,7 @@
return true;
}
- // If it's not, then check if it it should start a partition.
+ // If it's not, then check if it should start a partition.
if (shouldStartNewPartitionLocked(id)) {
if (sDebug) {
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
@@ -2922,7 +2933,7 @@
if (sDebug) {
Slog.d(TAG, "Set the response has expired.");
}
- mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+ mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists(
NOT_SHOWN_REASON_VIEW_CHANGED);
mPresentationStatsEventLogger.logAndEndEvent();
return;
@@ -2967,11 +2978,19 @@
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
- if (!isRequestSupportFillDialog(flags)) {
- mSessionFlags.mFillDialogDisabled = true;
- }
mPresentationStatsEventLogger.startNewEvent();
mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+ if (isRequestSupportFillDialog(flags)) {
+ // Set the default reason for now if the user doesn't trigger any focus event
+ // on the autofillable view. This can be changed downstream when more
+ // information is available or session is committed.
+ mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+ NOT_SHOWN_REASON_NO_FOCUS);
+ mStartedLogEventWithoutFocus = true;
+ } else {
+ mSessionFlags.mFillDialogDisabled = true;
+ mStartedLogEventWithoutFocus = false;
+ }
requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
@@ -3014,6 +3033,8 @@
}
break;
case ACTION_VIEW_ENTERED:
+ boolean startedEventWithoutFocus = mStartedLogEventWithoutFocus;
+ mStartedLogEventWithoutFocus = false;
if (sVerbose && virtualBounds != null) {
Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
}
@@ -3030,9 +3051,15 @@
return;
}
- mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
- NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
- mPresentationStatsEventLogger.logAndEndEvent();
+ // Previously, fill request will only start whenever a view is entered.
+ // With Fill Dialog, request starts prior to view getting entered. So, we can't end
+ // the event at this moment, otherwise we will be wrongly attributing fill dialog
+ // event as concluded.
+ if (!startedEventWithoutFocus) {
+ mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+ NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
+ mPresentationStatsEventLogger.logAndEndEvent();
+ }
if ((flags & FLAG_MANUAL_REQUEST) == 0) {
// Not a manual request
@@ -3060,9 +3087,22 @@
}
}
- mPresentationStatsEventLogger.startNewEvent();
- mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+ if (!startedEventWithoutFocus) {
+ mPresentationStatsEventLogger.startNewEvent();
+ mPresentationStatsEventLogger.maybeSetAutofillServiceUid(
+ getAutofillServiceUid());
+ }
if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
+ // If a new request was issued even if previously it was fill dialog request,
+ // we should end the log event, and start a new one. However, it leaves us
+ // susceptible to race condition. But since mPresentationStatsEventLogger is
+ // lock guarded, we should be safe.
+ if (startedEventWithoutFocus) {
+ mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.startNewEvent();
+ mPresentationStatsEventLogger.maybeSetAutofillServiceUid(
+ getAutofillServiceUid());
+ }
return;
}
@@ -3289,7 +3329,7 @@
if (requestShowInlineSuggestionsLocked(response, filterText)) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_INLINE_SHOWN);
- // TODO(b/137800469): Fix it to log showed only when IME asks for inflation,
+ // TODO(b/248378401): Fix it to log showed only when IME asks for inflation,
// rather than here where framework sends back the response.
mService.logDatasetShown(id, mClientState, UI_TYPE_INLINE);
@@ -3310,7 +3350,6 @@
synchronized (mLock) {
mService.logDatasetShown(id, mClientState, UI_TYPE_MENU);
-
mPresentationStatsEventLogger.maybeSetCountShown(
response.getDatasets(), mCurrentViewId);
mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a71f51a..e31c952 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -46,6 +46,7 @@
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -266,6 +267,7 @@
OP_PLAY_AUDIO,
OP_RECORD_AUDIO,
OP_CAMERA,
+ OP_VIBRATE,
};
private static final int MAX_UNFORWARDED_OPS = 10;
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 1bcc21e..43732d4 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.logcat;
+import static android.os.Process.getParentPid;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,6 +40,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.internal.app.LogAccessDialogActivity;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -45,6 +49,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -98,7 +103,7 @@
private final Injector mInjector;
private final Supplier<Long> mClock;
private final BinderService mBinderService;
- private final LogcatManagerServiceInternal mLocalService;
+ private final LogAccessDialogCallback mDialogCallback;
private final Handler mHandler;
private ActivityManagerInternal mActivityManagerInternal;
private ILogd mLogdService;
@@ -203,7 +208,8 @@
}
}
- final class LogcatManagerServiceInternal {
+ final class LogAccessDialogCallback extends ILogAccessDialogCallback.Stub {
+ @Override
public void approveAccessForClient(int uid, @NonNull String packageName) {
final LogAccessClient client = new LogAccessClient(uid, packageName);
if (DEBUG) {
@@ -213,6 +219,7 @@
mHandler.sendMessageAtTime(msg, mClock.get());
}
+ @Override
public void declineAccessForClient(int uid, @NonNull String packageName) {
final LogAccessClient client = new LogAccessClient(uid, packageName);
if (DEBUG) {
@@ -299,7 +306,7 @@
mInjector = injector;
mClock = injector.createClock();
mBinderService = new BinderService();
- mLocalService = new LogcatManagerServiceInternal();
+ mDialogCallback = new LogAccessDialogCallback();
mHandler = new LogAccessRequestHandler(injector.getLooper(), this);
}
@@ -308,15 +315,14 @@
try {
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
publishBinderService("logcat", mBinderService);
- publishLocalService(LogcatManagerServiceInternal.class, mLocalService);
} catch (Throwable t) {
Slog.e(TAG, "Could not start the LogcatManagerService.", t);
}
}
@VisibleForTesting
- LogcatManagerServiceInternal getLocalService() {
- return mLocalService;
+ LogAccessDialogCallback getDialogCallback() {
+ return mDialogCallback;
}
@VisibleForTesting
@@ -340,13 +346,6 @@
* access
*/
private String getPackageName(LogAccessRequest request) {
- if (mActivityManagerInternal != null) {
- String packageName = mActivityManagerInternal.getPackageNameByPid(request.mPid);
- if (packageName != null) {
- return packageName;
- }
- }
-
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
// Decline the logd access if PackageManager is null
@@ -355,15 +354,28 @@
}
String[] packageNames = pm.getPackagesForUid(request.mUid);
-
if (ArrayUtils.isEmpty(packageNames)) {
// Decline the logd access if the app name is unknown
Slog.e(TAG, "Unknown calling package name, declining the logd access");
return null;
}
- String firstPackageName = packageNames[0];
+ if (mActivityManagerInternal != null) {
+ int pid = request.mPid;
+ String packageName = mActivityManagerInternal.getPackageNameByPid(pid);
+ while ((packageName == null || !ArrayUtils.contains(packageNames, packageName))
+ && pid != -1) {
+ pid = getParentPid(pid);
+ packageName = mActivityManagerInternal.getPackageNameByPid(pid);
+ }
+ if (packageName != null && ArrayUtils.contains(packageNames, packageName)) {
+ return packageName;
+ }
+ }
+
+ Arrays.sort(packageNames);
+ String firstPackageName = packageNames[0];
if (firstPackageName == null || firstPackageName.isEmpty()) {
// Decline the logd access if the package name from uid is unknown
Slog.e(TAG, "Unknown calling package name, declining the logd access");
@@ -430,6 +442,7 @@
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_PENDING_TIMEOUT, client),
mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
final Intent mIntent = createIntent(client);
+ mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
}
@@ -530,6 +543,7 @@
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
intent.putExtra(Intent.EXTRA_UID, client.mUid);
+ intent.putExtra(LogAccessDialogActivity.EXTRA_CALLBACK, mDialogCallback.asBinder());
return intent;
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 7ad4d22..eebde36 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -110,12 +110,12 @@
final SuspendParams newSuspendParams =
new SuspendParams(dialogInfo, appExtras, launcherExtras);
- final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
- final IntArray changedUids = new IntArray(packageNames.length);
- final IntArray modifiedUids = new IntArray(packageNames.length);
final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
- ArraySet<String> modifiedPackages = new ArraySet<>();
+ final List<String> notifyPackagesList = new ArrayList<>(packageNames.length);
+ final IntArray notifyUids = new IntArray(packageNames.length);
+ final ArraySet<String> changedPackagesList = new ArraySet<>(packageNames.length);
+ final IntArray changedUids = new IntArray(packageNames.length);
final boolean[] canSuspend = suspended
? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid) : null;
@@ -143,21 +143,16 @@
final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
packageState.getUserStateOrDefault(userId).getSuspendParams();
- if (suspended) {
- if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) {
- final SuspendParams suspendParams = suspendParamsMap.get(packageName);
- // Skip if there's no changes
- if (suspendParams != null
- && Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
- && Objects.equals(suspendParams.getAppExtras(), appExtras)
- && Objects.equals(suspendParams.getLauncherExtras(),
- launcherExtras)) {
- // Carried over API behavior, must notify change even if no change
- changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
- continue;
- }
- }
+
+ SuspendParams oldSuspendParams = suspendParamsMap == null
+ ? null : suspendParamsMap.get(packageName);
+ boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
+
+ if (suspended && !changed) {
+ // Carried over API behavior, must notify change even if no change
+ notifyPackagesList.add(packageName);
+ notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ continue;
}
// If only the callingPackage is suspending this package,
@@ -166,18 +161,21 @@
&& CollectionUtils.size(suspendParamsMap) == 1
&& suspendParamsMap.containsKey(callingPackage);
if (suspended || packageUnsuspended) {
+ // Always notify of a suspend call + notify when fully unsuspended
+ notifyPackagesList.add(packageName);
+ notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ }
+
+ if (changed) {
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
}
-
- modifiedPackages.add(packageName);
- modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
}
mPm.commitPackageStateMutation(null, mutator -> {
- final int size = modifiedPackages.size();
+ final int size = changedPackagesList.size();
for (int index = 0; index < size; index++) {
- final String packageName = modifiedPackages.valueAt(index);
+ final String packageName = changedPackagesList.valueAt(index);
final PackageUserStateWrite userState = mutator.forPackage(packageName)
.userState(userId);
if (suspended) {
@@ -190,19 +188,19 @@
final Computer newSnapshot = mPm.snapshotComputer();
- if (!changedPackagesList.isEmpty()) {
- final String[] changedPackages = changedPackagesList.toArray(new String[0]);
+ if (!notifyPackagesList.isEmpty()) {
+ final String[] notifyPackages = notifyPackagesList.toArray(new String[0]);
sendPackagesSuspendedForUser(newSnapshot,
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
- changedPackages, changedUids.toArray(), userId);
- sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
+ notifyPackages, notifyUids.toArray(), userId);
+ sendMyPackageSuspendedOrUnsuspended(notifyPackages, suspended, userId);
mPm.scheduleWritePackageRestrictions(userId);
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
- if (!modifiedPackages.isEmpty()) {
+ if (!changedPackagesList.isEmpty()) {
sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
- modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
+ changedPackagesList.toArray(new String[0]), changedUids.toArray(), userId);
}
return unmodifiablePackages.toArray(new String[0]);
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index dc4bdaa..ce1157e 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.media.permission.Identity;
import android.media.permission.IdentityContext;
import android.media.soundtrigger.ModelParameterRange;
@@ -33,6 +34,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.LatencyTracker;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -65,9 +68,12 @@
public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
private static final String TAG = "SoundTriggerMiddlewareLogging";
private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
+ private final @NonNull Context mContext;
- public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareInternal delegate) {
+ public SoundTriggerMiddlewareLogging(@NonNull Context context,
+ @NonNull ISoundTriggerMiddlewareInternal delegate) {
mDelegate = delegate;
+ mContext = context;
}
@Override
@@ -298,6 +304,7 @@
int captureSession)
throws RemoteException {
try {
+ startKeyphraseEventLatencyTracking(event);
mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
logVoidReturn("onPhraseRecognition", modelHandle, event);
} catch (Exception e) {
@@ -347,6 +354,26 @@
logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
}
+ /**
+ * Starts the latency tracking log for keyphrase hotword invocation.
+ * The measurement covers from when the SoundTrigger HAL emits an event to when the
+ * {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
+ */
+ private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) {
+ String latencyTrackerTag = null;
+ if (event.phraseExtras.length > 0) {
+ latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id;
+ }
+ LatencyTracker latencyTracker = LatencyTracker.getInstance(mContext);
+ // To avoid adding cancel to all of the different failure modes between here and
+ // showing the system UI, we defensively cancel once.
+ // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel
+ // here if any error occurs.
+ latencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
+ latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION,
+ latencyTrackerTag);
+ }
+
@Override
public IBinder asBinder() {
return mCallbackDelegate.asBinder();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 1995e54..807ed14 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,14 +20,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseSoundModel;
-import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.SoundModel;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.Identity;
import android.media.permission.PermissionUtil;
import android.media.permission.SafeCloseable;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -226,12 +226,13 @@
HalFactory[] factories = new HalFactory[]{new DefaultHalFactory()};
publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
- new SoundTriggerMiddlewareService(new SoundTriggerMiddlewareLogging(
- new SoundTriggerMiddlewarePermission(
- new SoundTriggerMiddlewareValidation(
- new SoundTriggerMiddlewareImpl(factories,
- new AudioSessionProviderImpl())),
- getContext())), getContext()));
+ new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareLogging(getContext(),
+ new SoundTriggerMiddlewarePermission(
+ new SoundTriggerMiddlewareValidation(
+ new SoundTriggerMiddlewareImpl(factories,
+ new AudioSessionProviderImpl())),
+ getContext())), getContext()));
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9456f0f..2e1477d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -25,6 +25,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER;
@@ -1007,6 +1008,20 @@
isInLockTaskMode);
break;
}
+ case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: {
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer());
+ if (activity == null || activity.finishing) {
+ break;
+ }
+ if (activity.isVisible()) {
+ // Prevent the transition from being executed too early if the activity is
+ // visible.
+ activity.finishIfPossible("finish-activity-op", false /* oomAdj */);
+ } else {
+ activity.destroyIfPossible("finish-activity-op");
+ }
+ break;
+ }
case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"launchTask HierarchyOp");
@@ -1620,6 +1635,9 @@
organizer);
}
break;
+ case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
+ // Allow finish activity if it has the activity token.
+ break;
default:
// Other types of hierarchy changes are not allowed.
String msg = "Permission Denial: " + func + " from pid="
diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
index f330017..2cd5314 100644
--- a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.logcat;
+import static android.os.Process.INVALID_UID;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -28,6 +30,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
import android.os.ILogd;
import android.os.Looper;
import android.os.UserHandle;
@@ -69,10 +72,12 @@
@Mock
private ActivityManagerInternal mActivityManagerInternalMock;
@Mock
+ private PackageManager mPackageManagerMock;
+ @Mock
private ILogd mLogdMock;
private LogcatManagerService mService;
- private LogcatManagerService.LogcatManagerServiceInternal mLocalService;
+ private LogcatManagerService.LogAccessDialogCallback mDialogCallback;
private ContextWrapper mContextSpy;
private OffsettableClock mClock;
private TestLooper mTestLooper;
@@ -81,10 +86,17 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+ when(mActivityManagerInternalMock.getInstrumentationSourceUid(anyInt()))
+ .thenReturn(INVALID_UID);
+
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
-
+ when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
+ when(mPackageManagerMock.getPackagesForUid(APP1_UID)).thenReturn(
+ new String[]{APP1_PACKAGE_NAME});
+ when(mPackageManagerMock.getPackagesForUid(APP2_UID)).thenReturn(
+ new String[]{APP2_PACKAGE_NAME});
when(mActivityManagerInternalMock.getPackageNameByPid(APP1_PID)).thenReturn(
APP1_PACKAGE_NAME);
when(mActivityManagerInternalMock.getPackageNameByPid(APP2_PID)).thenReturn(
@@ -106,7 +118,7 @@
return mLogdMock;
}
});
- mLocalService = mService.getLocalService();
+ mDialogCallback = mService.getDialogCallback();
mService.onStart();
}
@@ -136,6 +148,20 @@
}
@Test
+ public void test_RequestFromBackground_ApprovedIfInstrumented() throws Exception {
+ when(mActivityManagerInternalMock.getInstrumentationSourceUid(APP1_UID))
+ .thenReturn(APP1_UID);
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_RECEIVER);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mContextSpy, never()).startActivityAsUser(any(), any());
+ }
+
+ @Test
public void test_RequestFromForegroundService_DeclinedWithoutPrompt() throws Exception {
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -181,7 +207,7 @@
mTestLooper.dispatchAll();
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
- mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -196,7 +222,7 @@
mTestLooper.dispatchAll();
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
- mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -214,7 +240,7 @@
verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
- mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -234,7 +260,7 @@
verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
- mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -249,7 +275,7 @@
ActivityManager.PROCESS_STATE_TOP);
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
mTestLooper.dispatchAll();
- mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
@@ -267,7 +293,7 @@
ActivityManager.PROCESS_STATE_TOP);
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
mTestLooper.dispatchAll();
- mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
@@ -287,7 +313,7 @@
ActivityManager.PROCESS_STATE_TOP);
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
mTestLooper.dispatchAll();
- mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
mService.getBinderService().startThread(APP2_UID, APP2_GID, APP2_PID, FD2);
@@ -304,7 +330,7 @@
ActivityManager.PROCESS_STATE_TOP);
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
mTestLooper.dispatchAll();
- mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
mTestLooper.dispatchAll();
advanceTime(LogcatManagerService.STATUS_EXPIRATION_TIMEOUT_MILLIS);
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 61cf8cc..1404de2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -730,6 +730,16 @@
}
@Test
+ public void testApplyTransaction_finishActivity() {
+ final ActivityRecord activity = createActivityRecord(mDisplayContent);
+
+ mTransaction.finishActivity(activity.token);
+ assertApplyTransactionAllowed(mTransaction);
+
+ assertTrue(activity.finishing);
+ }
+
+ @Test
public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
mController.unregisterOrganizer(mIOrganizer);
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index bde9c3d..a6e1a32 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -22,6 +22,7 @@
import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
+import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
@@ -185,7 +186,7 @@
final int mUser;
final Context mContext;
- @Nullable final AttentionManagerInternal mAttentionManagerInternal;
+ @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
this::setProximityMeters;
@@ -240,9 +241,11 @@
mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
- mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
- if (mAttentionManagerInternal != null) {
- mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+ if (ENABLE_PROXIMITY_RESULT) {
+ mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+ if (mAttentionManagerInternal != null) {
+ mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+ }
}
mLastRestartInstant = Instant.now();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 0ce0265..3da4711 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -95,6 +95,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.LatencyTracker;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -191,6 +192,8 @@
mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class);
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mServiceStub.systemRunning(isSafeMode());
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ mServiceStub.registerVoiceInteractionSessionListener(mLatencyLoggingListener);
}
}
@@ -2334,4 +2337,36 @@
}
};
}
+
+ /**
+ * End the latency tracking log for keyphrase hotword invocation.
+ * The measurement covers from when the SoundTrigger HAL emits an event, captured in
+ * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging}
+ * to when the {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
+ */
+ private final IVoiceInteractionSessionListener mLatencyLoggingListener =
+ new IVoiceInteractionSessionListener.Stub() {
+ @Override
+ public void onVoiceSessionShown() throws RemoteException {}
+
+ @Override
+ public void onVoiceSessionHidden() throws RemoteException {}
+
+ @Override
+ public void onVoiceSessionWindowVisibilityChanged(boolean visible)
+ throws RemoteException {
+ if (visible) {
+ LatencyTracker.getInstance(mContext)
+ .onActionEnd(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
+ }
+ }
+
+ @Override
+ public void onSetUiHints(Bundle args) throws RemoteException {}
+
+ @Override
+ public IBinder asBinder() {
+ return mServiceStub;
+ }
+ };
}