Merge "Add unlock animation for foldables" into tm-qpr-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c0e89d2..14bf532 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -797,6 +797,7 @@
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -2917,7 +2918,9 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index bbe99f5..226278c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1104,6 +1104,34 @@
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index a389223..a2fa139 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -58,13 +58,11 @@
setTitle(title);
}
- final Object callback = getIntent().getExtras().getBinder(EXTRA_CALLBACK);
- if (callback instanceof DreamService.DreamActivityCallbacks) {
- mCallback = (DreamService.DreamActivityCallbacks) callback;
+ final Bundle extras = getIntent().getExtras();
+ mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
+
+ if (mCallback != null) {
mCallback.onActivityCreated(this);
- } else {
- mCallback = null;
- finishAndRemoveTask();
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index d79ea89..2243072 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1409,6 +1409,10 @@
// Request the DreamOverlay be told to dream with dream's window
// parameters once the window has been attached.
mDreamStartOverlayConsumer = overlay -> {
+ if (mWindow == null) {
+ Slog.d(TAG, "mWindow is null");
+ return;
+ }
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
mDreamComponent.flattenToString(),
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d7480e5..543a22b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8774,7 +8774,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8782,6 +8783,9 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15586,7 +15590,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -25786,6 +25791,9 @@
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f9e8411..f6211fd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -82,6 +83,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -94,12 +96,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -871,6 +875,15 @@
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen(int[])},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
/**
@@ -958,6 +971,8 @@
mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -8426,6 +8441,9 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8442,6 +8460,69 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ inOutRect.offset(-bounds.left, -bounds.top);
+ }
+ }
+
+ /**
+ * Offset outLocation to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getLocationOnScreen(int[])}
+ */
+ public void applyViewLocationSandboxingIfNeeded(@Size(2) int[] outLocation) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ outLocation[0] -= bounds.left;
+ outLocation[1] -= bounds.top;
+ }
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ // System dialogs (e.g. ANR) can be created within System process, so handleBindApplication
+ // may be never called. This results into all app compat changes being enabled
+ // (see b/268007823) because AppCompatCallbacks.install() is never called with non-empty
+ // array.
+ // With ActivityThread.isSystem we verify that it is not the system process,
+ // then this CompatChange can take effect.
+ if (ActivityThread.isSystem()
+ || !CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // It is a system process or OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 17df585..a01c832 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -854,6 +854,42 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 72b9cd2..818a503 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -73,6 +73,7 @@
import java.io.PrintWriter;
import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
public final class ProcessState {
private static final String TAG = "ProcessStats";
@@ -1542,6 +1543,75 @@
proto.write(fieldId, procName);
}
+ /** Dumps the duration of each state to statsEventOutput. */
+ public void dumpStateDurationToStatsd(
+ int atomTag, ProcessStats processStats, StatsEventOutput statsEventOutput) {
+ long topMs = 0;
+ long fgsMs = 0;
+ long boundTopMs = 0;
+ long boundFgsMs = 0;
+ long importantForegroundMs = 0;
+ long cachedMs = 0;
+ long frozenMs = 0;
+ long otherMs = 0;
+ for (int i = 0, size = mDurations.getKeyCount(); i < size; i++) {
+ final int key = mDurations.getKeyAt(i);
+ final int type = SparseMappingTable.getIdFromKey(key);
+ int procStateIndex = type % STATE_COUNT;
+ long duration = mDurations.getValue(key);
+ switch (procStateIndex) {
+ case STATE_TOP:
+ topMs += duration;
+ break;
+ case STATE_BOUND_TOP_OR_FGS:
+ boundTopMs += duration;
+ break;
+ case STATE_FGS:
+ fgsMs += duration;
+ break;
+ case STATE_IMPORTANT_FOREGROUND:
+ case STATE_IMPORTANT_BACKGROUND:
+ importantForegroundMs += duration;
+ break;
+ case STATE_BACKUP:
+ case STATE_SERVICE:
+ case STATE_SERVICE_RESTARTING:
+ case STATE_RECEIVER:
+ case STATE_HEAVY_WEIGHT:
+ case STATE_HOME:
+ case STATE_LAST_ACTIVITY:
+ case STATE_PERSISTENT:
+ otherMs += duration;
+ break;
+ case STATE_CACHED_ACTIVITY:
+ case STATE_CACHED_ACTIVITY_CLIENT:
+ case STATE_CACHED_EMPTY:
+ cachedMs += duration;
+ break;
+ // TODO (b/261910877) Add support for tracking boundFgsMs and
+ // frozenMs.
+ }
+ }
+ statsEventOutput.write(
+ atomTag,
+ getUid(),
+ getName(),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ processStats.mTimePeriodEndUptime
+ - processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(topMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(fgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundTopMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundFgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(importantForegroundMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(cachedMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(frozenMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(otherMs));
+ }
+
/** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */
public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId,
String procName, int uid, long now,
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index d2b2f0a..f3ed09a 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -43,6 +43,7 @@
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.AssociationState.SourceKey;
import com.android.internal.app.procstats.AssociationState.SourceState;
+import com.android.internal.util.function.QuintConsumer;
import dalvik.system.VMRuntime;
@@ -56,6 +57,8 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -2389,6 +2392,79 @@
}
}
+ void forEachProcess(Consumer<ProcessState> consumer) {
+ final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+ for (int ip = 0, size = procMap.size(); ip < size; ip++) {
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final ProcessState processState = uids.valueAt(iu);
+ consumer.accept(processState);
+ }
+ }
+ }
+
+ void forEachAssociation(
+ QuintConsumer<AssociationState, Integer, String, SourceKey, SourceState> consumer) {
+ final ArrayMap<String, SparseArray<LongSparseArray<PackageState>>> pkgMap =
+ mPackages.getMap();
+ for (int ip = 0, size = pkgMap.size(); ip < size; ip++) {
+ final SparseArray<LongSparseArray<PackageState>> uids = pkgMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final int uid = uids.keyAt(iu);
+ final LongSparseArray<PackageState> versions = uids.valueAt(iu);
+ for (int iv = 0, versionsSize = versions.size(); iv < versionsSize; iv++) {
+ final PackageState state = versions.valueAt(iv);
+ for (int iasc = 0, ascSize = state.mAssociations.size();
+ iasc < ascSize;
+ iasc++) {
+ final String serviceName = state.mAssociations.keyAt(iasc);
+ final AssociationState asc = state.mAssociations.valueAt(iasc);
+ for (int is = 0, sourcesSize = asc.mSources.size();
+ is < sourcesSize;
+ is++) {
+ final SourceState src = asc.mSources.valueAt(is);
+ final SourceKey key = asc.mSources.keyAt(is);
+ consumer.accept(asc, uid, serviceName, key, src);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Dumps the stats of all processes to statsEventOutput. */
+ public void dumpProcessState(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachProcess(
+ (processState) -> {
+ if (processState.isMultiPackage()
+ && processState.getCommonProcess() != processState) {
+ return;
+ }
+ processState.dumpStateDurationToStatsd(atomTag, this, statsEventOutput);
+ });
+ }
+
+ /** Dumps all process association data to statsEventOutput. */
+ public void dumpProcessAssociation(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachAssociation(
+ (asc, serviceUid, serviceName, key, src) -> {
+ statsEventOutput.write(
+ atomTag,
+ key.mUid,
+ key.mProcess,
+ serviceUid,
+ serviceName,
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ mTimePeriodEndUptime - mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(src.mDuration),
+ src.mActiveCount,
+ asc.getProcessName());
+ });
+ }
+
private void dumpProtoPreamble(ProtoOutputStream proto) {
proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
diff --git a/core/java/com/android/internal/app/procstats/StatsEventOutput.java b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
new file mode 100644
index 0000000..b2e4054
--- /dev/null
+++ b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.procstats;
+
+import android.util.StatsEvent;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+
+/**
+ * A simple wrapper of FrameworkStatsLog.buildStatsEvent. This allows unit tests to mock out the
+ * dependency.
+ */
+public class StatsEventOutput {
+
+ List<StatsEvent> mOutput;
+
+ public StatsEventOutput(List<StatsEvent> output) {
+ mOutput = output;
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int uid,
+ String processName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int topSeconds,
+ int fgsSeconds,
+ int boundTopSeconds,
+ int boundFgsSeconds,
+ int importantForegroundSeconds,
+ int cachedSeconds,
+ int frozenSeconds,
+ int otherSeconds) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ uid,
+ processName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ topSeconds,
+ fgsSeconds,
+ boundTopSeconds,
+ boundFgsSeconds,
+ importantForegroundSeconds,
+ cachedSeconds,
+ frozenSeconds,
+ otherSeconds));
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int clientUid,
+ String processName,
+ int serviceUid,
+ String serviceName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int activeDurationUptimeSecs,
+ int activeCount,
+ String serviceProcessName) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ clientUid,
+ processName,
+ serviceUid,
+ serviceName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ activeDurationUptimeSecs,
+ activeCount,
+ serviceProcessName));
+ }
+}
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 3259201..8bedb89 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -25,7 +25,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:maxWidth="@dimen/toast_width"
- android:background="?android:attr/toastFrameBackground"
+ android:background="?android:attr/colorBackground"
android:elevation="@dimen/toast_elevation"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
diff --git a/core/res/res/layout/transient_notification_with_icon.xml b/core/res/res/layout/transient_notification_with_icon.xml
index e9b17df..0dfb3ad 100644
--- a/core/res/res/layout/transient_notification_with_icon.xml
+++ b/core/res/res/layout/transient_notification_with_icon.xml
@@ -22,7 +22,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:maxWidth="@dimen/toast_width"
- android:background="?android:attr/toastFrameBackground"
+ android:background="?android:attr/colorBackground"
android:elevation="@dimen/toast_elevation"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
new file mode 100644
index 0000000..9b9a84b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 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.procstats;
+
+import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import junit.framework.TestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+/** Provides test cases for ProcessStats. */
+public class ProcessStatsTest extends TestCase {
+
+ private static final String APP_1_PACKAGE_NAME = "com.android.testapp";
+ private static final int APP_1_UID = 5001;
+ private static final long APP_1_VERSION = 10;
+ private static final String APP_1_PROCESS_NAME = "com.android.testapp.p";
+ private static final String APP_1_SERVICE_NAME = "com.android.testapp.service";
+
+ private static final String APP_2_PACKAGE_NAME = "com.android.testapp2";
+ private static final int APP_2_UID = 5002;
+ private static final long APP_2_VERSION = 30;
+ private static final String APP_2_PROCESS_NAME = "com.android.testapp2.p";
+
+ private static final long NOW_MS = 123000;
+ private static final int DURATION_SECS = 6;
+
+ @Mock StatsEventOutput mStatsEventOutput;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ }
+
+ @SmallTest
+ public void testDumpProcessState() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processStats.getProcessStateLocked(
+ APP_2_PACKAGE_NAME, APP_2_UID, APP_2_VERSION, APP_2_PROCESS_NAME);
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testNonZeroProcessStateDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setCombinedState(STATE_TOP, NOW_MS);
+ processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(DURATION_SECS),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testDumpProcessAssociation() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ AssociationState associationState =
+ processStats.getAssociationStateLocked(
+ APP_1_PACKAGE_NAME,
+ APP_1_UID,
+ APP_1_VERSION,
+ APP_1_PROCESS_NAME,
+ APP_1_SERVICE_NAME);
+ AssociationState.SourceState sourceState =
+ associationState.startSource(APP_2_UID, APP_2_PROCESS_NAME, APP_2_PACKAGE_NAME);
+ sourceState.stop();
+ processStats.dumpProcessAssociation(
+ FrameworkStatsLog.PROCESS_ASSOCIATION, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_ASSOCIATION),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ eq(APP_1_UID),
+ eq(APP_1_SERVICE_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(APP_1_PROCESS_NAME));
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index caa118a..e1c9b3c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -517,6 +517,12 @@
<permission name="android.permission.BIND_WALLPAPER"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.wallpaper">
+ <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+ <permission name="android.permission.BIND_WALLPAPER"/>
+ <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.dynsystem">
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
diff --git a/libs/WindowManager/Shell/res/color/split_divider_background.xml b/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
similarity index 83%
rename from libs/WindowManager/Shell/res/color/split_divider_background.xml
rename to libs/WindowManager/Shell/res/color-night/taskbar_background.xml
index 0499808..9473cdd6 100644
--- a/libs/WindowManager/Shell/res/color/split_divider_background.xml
+++ b/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -14,6 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+<!-- Should be the same as in packages/apps/Launcher3/res/color-night-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_500" android:lStar="15" />
</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
index b3d26029..0e165fc 100644
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml
@@ -16,5 +16,5 @@
-->
<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="95" />
</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-night/colors.xml b/libs/WindowManager/Shell/res/values-night/colors.xml
index 83c4d93..5c6bb57 100644
--- a/libs/WindowManager/Shell/res/values-night/colors.xml
+++ b/libs/WindowManager/Shell/res/values-night/colors.xml
@@ -15,6 +15,7 @@
-->
<resources>
+ <color name="docked_divider_handle">#ffffff</color>
<!-- Bubbles -->
<color name="bubbles_icon_tint">@color/GM2_grey_200</color>
<!-- Splash screen-->
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 965ab15..6fb70006 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -17,7 +17,8 @@
*/
-->
<resources>
- <color name="docked_divider_handle">#ffffff</color>
+ <color name="docked_divider_handle">#000000</color>
+ <color name="split_divider_background">@color/taskbar_background</color>
<drawable name="forced_resizable_background">#59000000</drawable>
<color name="minimize_dock_shadow_start">#60000000</color>
<color name="minimize_dock_shadow_end">#00000000</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 22587f4..8b4ac1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -39,6 +39,9 @@
*
* Note that most of the implementation here inherits from
* {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
*/
public class DevicePostureController {
@IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
new file mode 100644
index 0000000..bf226283
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+ DevicePostureController.OnDevicePostureChangedListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+ private final Context mContext;
+
+ private final DevicePostureController mDevicePostureController;
+
+ private final DisplayController mDisplayController;
+
+ private final ShellExecutor mMainExecutor;
+
+ private final Set<Integer> mTabletopModeRotations = new ArraySet<>();
+
+ private final List<OnTabletopModeChangedListener> mListeners = new ArrayList<>();
+
+ @VisibleForTesting
+ final Runnable mOnEnterTabletopModeCallback = () -> {
+ if (isInTabletopMode()) {
+ // We are still in tabletop mode, go ahead.
+ mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+ }
+ };
+
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ @Surface.Rotation
+ private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+ /**
+ * Track the last callback value for {@link OnTabletopModeChangedListener}.
+ * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+ */
+ private Boolean mLastIsInTabletopModeForCallback;
+
+ public TabletopModeController(Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mDevicePostureController = postureController;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @VisibleForTesting
+ void onInit() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+ final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_deviceTabletopRotations);
+ if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "No valid config_deviceTabletopRotations, can not tell"
+ + " tabletop mode in WMShell");
+ return;
+ }
+ for (int angle : deviceTabletopRotations) {
+ switch (angle) {
+ case 0:
+ mTabletopModeRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopModeRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopModeRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopModeRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ break;
+ }
+ }
+ }
+
+ /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+ public void registerOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ if (listener == null || mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onTabletopModeChanged(isInTabletopMode());
+ }
+
+ /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+ public void unregisterOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mDevicePosture != posture) {
+ onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+ }
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+ if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+ onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+ }
+ }
+
+ private void onDevicePostureOrDisplayRotationChanged(
+ @DevicePostureController.DevicePostureInt int newPosture,
+ @Surface.Rotation int newDisplayRotation) {
+ final boolean wasInTabletopMode = isInTabletopMode();
+ mDevicePosture = newPosture;
+ mDisplayRotation = newDisplayRotation;
+ final boolean couldBeInTabletopMode = isInTabletopMode();
+ mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+ if (!wasInTabletopMode && couldBeInTabletopMode) {
+ // May enter tabletop mode, but we need to wait for additional time since this
+ // could be an intermediate state.
+ mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+ } else {
+ // Cancel entering tabletop mode if any condition's changed.
+ mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+ }
+ }
+
+ private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+ return posture == DEVICE_POSTURE_HALF_OPENED;
+ }
+
+ private boolean isInTabletopMode() {
+ return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+ }
+
+ private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+ if (mLastIsInTabletopModeForCallback == null
+ || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+ mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+ mLastIsInTabletopModeForCallback = isInTabletopMode;
+ }
+ }
+
+ /**
+ * Listener interface for tabletop mode change.
+ */
+ public interface OnTabletopModeChangedListener {
+ /**
+ * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+ * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+ */
+ void onTabletopModeChanged(boolean isInTabletopMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index abb357c..bdf0ac2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -247,11 +247,11 @@
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
- if (mScreenshot != null) {
- if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
- mScreenshotAnimator.cancel();
- }
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ if (mScreenshot != null) {
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
@@ -321,6 +321,10 @@
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mTempRect.set(mOldBounds);
mTempRect.offsetTo(0, 0);
mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
@@ -333,6 +337,10 @@
if (screenshot == null || !screenshot.isValid()) return;
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mScreenshot = screenshot;
t.reparent(screenshot, mHostLeash);
t.setLayer(screenshot, Integer.MAX_VALUE - 1);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f616e6f..ffc56b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -120,6 +120,7 @@
private int mOrientation;
private int mRotation;
private int mDensity;
+ private int mUiMode;
private final boolean mDimNonImeSide;
private ValueAnimator mDividerFlingAnimator;
@@ -295,10 +296,12 @@
final Rect rootBounds = configuration.windowConfiguration.getBounds();
final int orientation = configuration.orientation;
final int density = configuration.densityDpi;
+ final int uiMode = configuration.uiMode;
if (mOrientation == orientation
&& mRotation == rotation
&& mDensity == density
+ && mUiMode == uiMode
&& mRootBounds.equals(rootBounds)) {
return false;
}
@@ -310,6 +313,7 @@
mRootBounds.set(rootBounds);
mRotation = rotation;
mDensity = density;
+ mUiMode = uiMode;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
updateDividerConfig(mContext);
initDividerPosition(mTempRect);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 72dc771..ef21c7e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
@@ -171,6 +172,18 @@
@WMSingleton
@Provides
+ static TabletopModeController provideTabletopModeController(
+ Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new TabletopModeController(
+ context, shellInit, postureController, displayController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 75f9a4c..c9b3a1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -50,6 +50,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7d3e7ca..2f0cb7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -764,17 +764,9 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- if (shortcutInfo1 != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
- } else {
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- }
- mSyncQueue.queue(wct);
+ // Launching a solo intent or shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
+ options1, adapter, wct);
return;
}
@@ -797,13 +789,9 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo intent as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
+ adapter, wct);
return;
}
@@ -822,13 +810,8 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
return;
}
@@ -838,6 +821,49 @@
instanceId);
}
+ private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
+ @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
+ @Nullable Bundle options, RemoteAnimationAdapter adapter,
+ WindowContainerTransaction wct) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ addActivityOptions(options, null /* launchTarget */);
+ if (shortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
+ } else if (pendingIntent != null) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options);
+ } else {
+ Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
+ }
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
@@ -894,23 +920,25 @@
if (options == null) options = new Bundle();
addActivityOptions(options, mMainStage);
- options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
-
- // TODO(b/268008375): Merge APIs to start a split pair into one.
- if (mainTaskId != INVALID_TASK_ID) {
- wct.startTask(mainTaskId, options);
- } else if (mainShortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
- } else {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
- }
-
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
- mSyncQueue.queue(wct);
+ // TODO(b/268008375): Merge APIs to start a split pair into one.
+ if (mainTaskId != INVALID_TASK_ID) {
+ options = wrapAsSplitRemoteAnimation(adapter, options);
+ wct.startTask(mainTaskId, options);
+ mSyncQueue.queue(wct);
+ } else {
+ if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ } else {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ }
+ mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
+ }
+
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
});
@@ -967,6 +995,54 @@
return activityOptions.toBundle();
}
+ private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
+ RemoteAnimationAdapter adapter) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent offset.
+ t.setPosition(apps[i].leash, 0, 0);
+ }
+ }
+ t.apply();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ onRemoteAnimationFinished(apps);
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ return transition;
+ }
+
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
@@ -993,6 +1069,27 @@
}
}
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ mIsDividerRemoteAnimating = false;
+ mShouldUpdateRecents = true;
+ mSplitRequest = null;
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
+ return;
+ }
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -1687,7 +1784,9 @@
// Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ if (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping) {
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ }
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 0000000..96d202c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+ // It's considered tabletop mode if the display rotation angle matches what's in this array.
+ // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+ private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+ 90 /* Surface.ROTATION_90 */,
+ 270 /* Surface.ROTATION_270 */
+ };
+
+ private TestShellExecutor mMainExecutor;
+
+ private Configuration mConfiguration;
+
+ private TabletopModeController mPipTabletopController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private Resources mResources;
+
+ @Mock
+ private DevicePostureController mDevicePostureController;
+
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+ .thenReturn(TABLETOP_MODE_ROTATIONS);
+ when(mContext.getResources()).thenReturn(mResources);
+ mMainExecutor = new TestShellExecutor();
+ mConfiguration = new Configuration();
+ mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+ mDevicePostureController, mDisplayController, mMainExecutor);
+ mPipTabletopController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(false);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(true);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+ clearInvocations(mOnTabletopModeChangedListener);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ verifyZeroInteractions(mOnTabletopModeChangedListener);
+ }
+
+ // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+ @Test
+ public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+ @Test
+ public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
index 5ecec4d..3125f08 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
@@ -72,6 +72,7 @@
public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
private final DreamManager mDreamManager;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -81,8 +82,10 @@
@Inject
public LowLightDreamManager(
DreamManager dreamManager,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
@Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
mDreamManager = dreamManager;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mLowLightDreamComponent = lowLightDreamComponent;
}
@@ -111,7 +114,9 @@
mAmbientLightMode = ambientLightMode;
- mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
- ? mLowLightDreamComponent : null);
+ boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
+ mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
+ () -> mDreamManager.setSystemDreamComponent(
+ shouldEnterLowLight ? mLowLightDreamComponent : null));
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
new file mode 100644
index 0000000..874a2d5
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 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.dream.lowlight;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+public class LowLightTransitionCoordinator {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ public interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ Animator onBeforeEnterLowLight();
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ public interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ Animator onBeforeExitLowLight();
+ }
+
+ private LowLightEnterListener mLowLightEnterListener;
+ private LowLightExitListener mLowLightExitListener;
+
+ @Inject
+ public LowLightTransitionCoordinator() {
+ }
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
+ mLowLightEnterListener = lowLightEnterListener;
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
+ mLowLightExitListener = lowLightExitListener;
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light.
+ *
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ * @param callback callback that will be run after listeners complete.
+ */
+ void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
+ Animator animator = null;
+
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener.onBeforeEnterLowLight();
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener.onBeforeExitLowLight();
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ callback.run();
+ }
+ });
+ } else {
+ callback.run();
+ }
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
index 91a170f..4b95d8c 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
@@ -21,7 +21,10 @@
import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -44,44 +47,52 @@
private DreamManager mDreamManager;
@Mock
+ private LowLightTransitionCoordinator mTransitionCoordinator;
+
+ @Mock
private ComponentName mDreamComponent;
+ LowLightDreamManager mLowLightDreamManager;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing.
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(1)).run();
+ return null;
+ }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(),
+ any(Runnable.class));
+
+ mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator,
+ mDreamComponent);
}
@Test
public void setAmbientLightMode_lowLight_setSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
}
@Test
public void setAmbientLightMode_regularLight_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
-
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
verify(mDreamManager).setSystemDreamComponent(null);
}
@Test
public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
// Set to low light first.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
clearInvocations(mDreamManager);
// Return to default unknown mode.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
verify(mDreamManager).setSystemDreamComponent(null);
}
@@ -89,7 +100,7 @@
@Test
public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- null /*dream component*/);
+ mTransitionCoordinator, null /*dream component*/);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
new file mode 100644
index 0000000..81e1e33
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.dream.lowlight;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightTransitionCoordinatorTest {
+ @Mock
+ private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
+
+ @Mock
+ private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
+
+ @Mock
+ private Animator mAnimator;
+
+ @Captor
+ private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor;
+
+ @Mock
+ private Runnable mRunnable;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void onEnterCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verify(mEnterListener).onBeforeEnterLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void onExitCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightExitListener(mExitListener);
+
+ coordinator.notifyBeforeLowLightTransition(false, mRunnable);
+
+ verify(mExitListener).onBeforeExitLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void listenerNotCalledAfterRemoval() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+ coordinator.setLowLightEnterListener(null);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verifyZeroInteractions(mEnterListener);
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void runnableCalledAfterAnimationEnds() {
+ when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
+
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+ verifyZeroInteractions(mRunnable);
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
+ verify(mRunnable).run();
+ }
+}
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 9ca3910..2273f81 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1,2 +1,4 @@
michaelwr@google.com
santoscordon@google.com
+chaviw@google.com
+nmusgrave@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 2903288..f0a8211 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -19,14 +19,15 @@
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.util.MathUtils
+import android.util.MathUtils.abs
+import java.lang.Float.max
+import java.lang.Float.min
private const val TAG_WGHT = "wght"
private const val TAG_ITAL = "ital"
-private const val FONT_WEIGHT_MAX = 1000f
-private const val FONT_WEIGHT_MIN = 0f
-private const val FONT_WEIGHT_ANIMATION_STEP = 10f
private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
+private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100
private const val FONT_ITALIC_MAX = 1f
private const val FONT_ITALIC_MIN = 0f
@@ -118,14 +119,17 @@
lerp(startAxes, endAxes) { tag, startValue, endValue ->
when (tag) {
// TODO: Good to parse 'fvar' table for retrieving default value.
- TAG_WGHT ->
- adjustWeight(
+ TAG_WGHT -> {
+ adaptiveAdjustWeight(
MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
progress
- )
+ ),
+ startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
+ endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
)
+ }
TAG_ITAL ->
adjustItalic(
MathUtils.lerp(
@@ -205,10 +209,14 @@
return result
}
- // For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
+ // For the performance reasons, we animate weight with adaptive step. This helps
// Cache hit ratio in the Skia glyph cache.
- private fun adjustWeight(value: Float) =
- coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+ // The reason we don't use fix step is because the range of weight axis is not normalized,
+ // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step
+ private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float {
+ val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F)
+ return coerceInWithStep(value, min(start, end), max(start, end), step)
+ }
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index a08b598..7fe94d3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -190,6 +190,7 @@
* @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to
* the TextInterpolator
+ * @param strokeWidth an optional paint stroke width
* @param animate an optional boolean indicating true for showing style transition as animation,
* false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -201,6 +202,7 @@
weight: Int = -1,
textSize: Float = -1f,
color: Int? = null,
+ strokeWidth: Float = -1f,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
@@ -254,6 +256,9 @@
if (color != null) {
textInterpolator.targetPaint.color = color
}
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
textInterpolator.onTargetPaintModified()
if (animate) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 468a8b1..3eb7fd8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -473,6 +473,7 @@
// TODO(172943390): Add other interpolation or support custom interpolator.
out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
out.color = ColorUtils.blendARGB(from.color, to.color, progress)
+ out.strokeWidth = MathUtils.lerp(from.strokeWidth, to.strokeWidth, progress)
}
// Shape the text and stores the result to out argument.
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index f64ea45..459a38e 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,6 +25,7 @@
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.regex.Pattern
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UElement
@@ -36,22 +37,38 @@
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
- if (node.qualifiedName !in DEMOTING_ANNOTATION) {
- return
+ // Annotations having int bugId field
+ if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
+ val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
+ if (bugId <= 0) {
+ val location = context.getLocation(node)
+ val message = "Please attach a bug id to track demoted test"
+ context.report(ISSUE, node, location, message)
+ }
}
- val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
- if (bugId <= 0) {
- val location = context.getLocation(node)
- val message = "Please attach a bug id to track demoted test"
- context.report(ISSUE, node, location, message)
+ // @Ignore has a String field for reason
+ if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
+ val reason = node.findAttributeValue("value")!!.evaluate() as String
+ val bugPattern = Pattern.compile("b/\\d+")
+ if (!bugPattern.matcher(reason).find()) {
+ val location = context.getLocation(node)
+ val message = "Please attach a bug (e.g. b/123) to track demoted test"
+ context.report(ISSUE, node, location, message)
+ }
}
}
}
}
companion object {
- val DEMOTING_ANNOTATION =
- listOf("androidx.test.filters.FlakyTest", "android.platform.test.annotations.FlakyTest")
+ val DEMOTING_ANNOTATION_BUG_ID =
+ listOf(
+ "androidx.test.filters.FlakyTest",
+ "android.platform.test.annotations.FlakyTest",
+ "android.platform.test.rule.PlatinumRule.Platinum",
+ )
+
+ const val DEMOTING_ANNOTATION_IGNORE = "org.junit.Ignore"
@JvmField
val ISSUE: Issue =
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 557c300..63eb263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -127,6 +127,139 @@
)
}
+ @Test
+ fun testExcludeDevices_withBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.rule.PlatinumRule.Platinum;
+
+ @Platinum(devices = "foo,bar", bugId = 123)
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testExcludeDevices_withoutBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.rule.PlatinumRule.Platinum;
+
+ @Platinum(devices = "foo,bar")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug id to track demoted test [DemotingTestWithoutBug]
+ @Platinum(devices = "foo,bar")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testIgnore_withBug() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore("Blocked by b/123.")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testIgnore_withoutBug() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+ @Ignore
+ ~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore("Not ready")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+ @Ignore("Not ready")
+ ~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
private val filtersFlakyTestStub: TestFile =
java(
"""
@@ -147,5 +280,34 @@
}
"""
)
- private val stubs = arrayOf(filtersFlakyTestStub, annotationsFlakyTestStub)
+ private val annotationsPlatinumStub: TestFile =
+ java(
+ """
+ package android.platform.test.rule;
+
+ public class PlatinumRule {
+ public @interface Platinum {
+ String devices();
+ int bugId() default -1;
+ }
+ }
+ """
+ )
+ private val annotationsIgnoreStub: TestFile =
+ java(
+ """
+ package org.junit;
+
+ public @interface Ignore {
+ String value() default "";
+ }
+ """
+ )
+ private val stubs =
+ arrayOf(
+ filtersFlakyTestStub,
+ annotationsFlakyTestStub,
+ annotationsPlatinumStub,
+ annotationsIgnoreStub
+ )
}
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index f1aa544..a4e7a5f 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,6 +27,4 @@
<integer name="scaled_password_text_size">26</integer>
<dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 992d143..edd6eff 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -123,9 +123,7 @@
<dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_y_trans">80dp</dimen>
<!-- 2 * the margin + size should equal the plus_margin -->
<dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
new file mode 100644
index 0000000..d123caf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -0,0 +1,12 @@
+<vector android:height="11dp" android:viewportHeight="12"
+ android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <group>
+ <clip-path android:pathData="M0,0.5h22v11h-22z"/>
+ <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+ <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+ <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+ <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+ <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+ <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 4f38e60..908aac4 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,8 @@
<dimen name="controls_header_horizontal_padding">12dp</dimen>
<dimen name="controls_content_margin_horizontal">16dp</dimen>
+
+ <!-- Bouncer user switcher margins -->
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index b98165f..ca62d28 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -21,6 +21,6 @@
<!-- Space between status view and notification shelf -->
<dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
<dimen name="keyguard_clock_top_margin">80dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">155dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">85dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ca4217f..ac4c492 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -207,6 +207,11 @@
<color name="control_thumbnail_shadow_color">@*android:color/black</color>
<color name="controls_task_view_bg">#CC191C1D</color>
+ <!-- Keyboard backlight indicator-->
+ <color name="backlight_indicator_step_filled">#F6E388</color>
+ <color name="backlight_indicator_step_empty">#494740</color>
+ <color name="backlight_indicator_background">#32302A</color>
+
<!-- Docked misalignment message -->
<color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 3f84ddb..f545dae0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -774,7 +774,7 @@
<!-- Duration in milliseconds of the dream in complications fade-in animation. -->
<integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
<!-- Duration in milliseconds of the y-translation animation when entering a dream -->
- <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
+ <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer>
<!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3b8f1a7..35fc9b6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1653,6 +1653,19 @@
<dimen name="media_output_broadcast_info_summary_height">20dp</dimen>
<dimen name="media_output_broadcast_info_edit">18dp</dimen>
+ <!-- Keyboard backlight indicator-->
+ <dimen name="backlight_indicator_root_corner_radius">48dp</dimen>
+ <dimen name="backlight_indicator_root_vertical_padding">8dp</dimen>
+ <dimen name="backlight_indicator_root_horizontal_padding">4dp</dimen>
+ <dimen name="backlight_indicator_icon_width">22dp</dimen>
+ <dimen name="backlight_indicator_icon_height">11dp</dimen>
+ <dimen name="backlight_indicator_icon_left_margin">2dp</dimen>
+ <dimen name="backlight_indicator_step_width">52dp</dimen>
+ <dimen name="backlight_indicator_step_height">40dp</dimen>
+ <dimen name="backlight_indicator_step_horizontal_margin">4dp</dimen>
+ <dimen name="backlight_indicator_step_small_radius">4dp</dimen>
+ <dimen name="backlight_indicator_step_large_radius">48dp</dimen>
+
<!-- Broadcast dialog -->
<dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
<dimen name="broadcast_dialog_title_text_size">24sp</dimen>
@@ -1701,4 +1714,9 @@
it is long-pressed.
-->
<dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
+
+
+ <!-- Bouncer user switcher margins -->
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8be5998..9a9f510 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2258,7 +2258,7 @@
<!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
- <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
+ <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string>
<!-- Shows in a dialog presented to the user to authorize this app removal from a Device
controls panel [CHAR LIMIT=NONE] -->
@@ -2318,7 +2318,7 @@
<!-- Title of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
<string name="controls_settings_trivial_controls_dialog_title">Control devices from lock screen?</string>
<!-- Message of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
- <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet.\n\nYour device app determines which devices can be controlled in this way.</string>
+ <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet. Your device app determines which devices can be controlled in this way.</string>
<!-- Neutral button title of the controls dialog [CHAR LIMIT=NONE] -->
<string name="controls_settings_dialog_neutral_button">No thanks</string>
<!-- Positive button title of the controls dialog [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a581aa..482158e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -91,6 +91,22 @@
val sampledRegion = calculateSampledRegion(sampledView)
val regions = ArrayList<RectF>()
val sampledRegionWithOffset = convertBounds(sampledRegion)
+
+ if (
+ sampledRegionWithOffset.left < 0.0 ||
+ sampledRegionWithOffset.right > 1.0 ||
+ sampledRegionWithOffset.top < 0.0 ||
+ sampledRegionWithOffset.bottom > 1.0
+ ) {
+ android.util.Log.e(
+ "RegionSampler",
+ "view out of bounds: $sampledRegion | " +
+ "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
+ Exception()
+ )
+ return
+ }
+
regions.add(sampledRegionWithOffset)
wallpaperManager?.removeOnColorsChangedListener(this)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 359da13..5b27c40 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -29,8 +29,11 @@
import android.annotation.DrawableRes;
import android.annotation.SuppressLint;
import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -86,6 +89,7 @@
private RotationButton mRotationButton;
private boolean mIsRecentsAnimationRunning;
+ private boolean mDocked;
private boolean mHomeRotationEnabled;
private int mLastRotationSuggestion;
private boolean mPendingRotationSuggestion;
@@ -123,6 +127,12 @@
() -> mPendingRotationSuggestion = false;
private Animator mRotateHideAnimator;
+ private final BroadcastReceiver mDockedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateDockedState(intent);
+ }
+ };
private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
@Override
@@ -136,7 +146,8 @@
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (rotationLocked || mRotationButton.isVisible()) {
- if (shouldOverrideUserLockPrefs(rotation) && rotationLocked) {
+ // Do not allow a change in rotation to set user rotation when docked.
+ if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
setRotationLockedAtAngle(rotation);
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
@@ -214,6 +225,10 @@
}
mListenersRegistered = true;
+
+ updateDockedState(mContext.registerReceiver(mDockedReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+
try {
WindowManagerGlobal.getWindowManagerService()
.watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
@@ -234,6 +249,8 @@
}
mListenersRegistered = false;
+
+ mContext.unregisterReceiver(mDockedReceiver);
try {
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
} catch (RemoteException e) {
@@ -345,6 +362,15 @@
updateRotationButtonStateInOverview();
}
+ private void updateDockedState(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+
+ mDocked = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED)
+ != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+
private void updateRotationButtonStateInOverview() {
if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
setRotateSuggestionButtonState(false, true /* hideImmediately */);
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 8323d09..f005bab 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,8 @@
import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
@@ -35,7 +37,8 @@
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
@Binds
- abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+ @IntoSet
+ abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
@Module
companion object {
@@ -44,5 +47,10 @@
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
+
+ @JvmStatic
+ @Provides
+ @Named(ConditionalRestarter.RESTART_DELAY)
+ fun provideRestartDelaySec(): Long = 1
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 87beff7..927d4604b 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,9 @@
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
@@ -29,5 +32,18 @@
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
@Binds
- abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
+ @IntoSet
+ abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+
+ @Binds
+ @IntoSet
+ abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @Named(ConditionalRestarter.RESTART_DELAY)
+ fun provideRestartDelaySec(): Long = 30
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 92ee373..4aaa566 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
+import android.graphics.Rect
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
@@ -119,10 +120,6 @@
private val mLayoutChangedListener =
object : View.OnLayoutChangeListener {
- private var currentSmallClockView: View? = null
- private var currentLargeClockView: View? = null
- private var currentSmallClockLocation = IntArray(2)
- private var currentLargeClockLocation = IntArray(2)
override fun onLayoutChange(
view: View?,
@@ -135,6 +132,8 @@
oldRight: Int,
oldBottom: Int
) {
+ view?.removeOnLayoutChangeListener(this)
+
val parent = (view?.parent) as FrameLayout
// don't pass in negative bounds when clocks are in transition state
@@ -142,31 +141,12 @@
return
}
- // SMALL CLOCK
- if (parent.id == R.id.lockscreen_clock_view) {
- // view bounds have changed due to clock size changing (i.e. different character
- // widths)
- // AND/OR the view has been translated when transitioning between small and
- // large clock
- if (
- view != currentSmallClockView ||
- !view.locationOnScreen.contentEquals(currentSmallClockLocation)
- ) {
- currentSmallClockView = view
- currentSmallClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
- }
- // LARGE CLOCK
- else if (parent.id == R.id.lockscreen_clock_view_large) {
- if (
- view != currentLargeClockView ||
- !view.locationOnScreen.contentEquals(currentLargeClockLocation)
- ) {
- currentLargeClockView = view
- currentLargeClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
+ val currentViewRect = Rect(left, top, right, bottom)
+ val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
+
+ if (currentViewRect.width() != oldViewRect.width() ||
+ currentViewRect.height() != oldViewRect.height()) {
+ updateRegionSampler(view)
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index fe8b8c9..c98e9b4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -40,7 +40,7 @@
var keyguardGoingAway: Boolean = false,
var listeningForFaceAssistant: Boolean = false,
var occludingAppRequestingFaceAuth: Boolean = false,
- val postureAllowsListening: Boolean = false,
+ var postureAllowsListening: Boolean = false,
var primaryUser: Boolean = false,
var secureCameraLaunched: Boolean = false,
var supportsDetect: Boolean = false,
@@ -70,6 +70,7 @@
listeningForFaceAssistant.toString(),
occludingAppRequestingFaceAuth.toString(),
primaryUser.toString(),
+ postureAllowsListening.toString(),
secureCameraLaunched.toString(),
supportsDetect.toString(),
switchingUser.toString(),
@@ -109,6 +110,7 @@
listeningForFaceAssistant = model.listeningForFaceAssistant
occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth
primaryUser = model.primaryUser
+ postureAllowsListening = model.postureAllowsListening
secureCameraLaunched = model.secureCameraLaunched
supportsDetect = model.supportsDetect
switchingUser = model.switchingUser
@@ -152,6 +154,7 @@
"listeningForFaceAssistant",
"occludingAppRequestingFaceAuth",
"primaryUser",
+ "postureAllowsListening",
"secureCameraLaunched",
"supportsDetect",
"switchingUser",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 67e3400..0394754 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -217,9 +217,11 @@
private void animate(float progress) {
Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
+ float standardProgress = standardDecelerate.getInterpolation(progress);
mBouncerMessageView.setTranslationY(
- mYTrans - mYTrans * standardDecelerate.getInterpolation(progress));
+ mYTrans - mYTrans * standardProgress);
+ mBouncerMessageView.setAlpha(standardProgress);
for (int i = 0; i < mViews.length; i++) {
View[] row = mViews[i];
@@ -236,7 +238,7 @@
view.setAlpha(scaledProgress);
int yDistance = mYTrans + mYTransOffset * i;
view.setTranslationY(
- yDistance - (yDistance * standardDecelerate.getInterpolation(progress)));
+ yDistance - (yDistance * standardProgress));
if (view instanceof NumPadAnimationListener) {
((NumPadAnimationListener) view).setProgress(scaledProgress);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 66d5d09..ba5a8c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -39,6 +39,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
@@ -1067,10 +1068,14 @@
int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation);
+ AnimatorSet anims = new AnimatorSet();
ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
- yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE);
- yAnim.setDuration(500);
- yAnim.start();
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
+ 0f);
+
+ anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+ anims.playTogether(alphaAnim, yAnim);
+ anims.start();
}
private void setupUserSwitcher() {
@@ -1220,8 +1225,7 @@
constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
- constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
- yTrans);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c32b853..2c2caea 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -127,6 +127,7 @@
private View.OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event);
private ActivityStarter.OnDismissAction mDismissAction;
private Runnable mCancelAction;
+ private boolean mWillRunDismissFromKeyguard;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -262,8 +263,10 @@
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
+ mWillRunDismissFromKeyguard = false;
if (mDismissAction != null) {
deferKeyguardDone = mDismissAction.onDismiss();
+ mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
mDismissAction = null;
mCancelAction = null;
}
@@ -526,6 +529,13 @@
}
/**
+ * @return will the dismissal run from the keyguard layout (instead of from bouncer)
+ */
+ public boolean willRunDismissFromKeyguard() {
+ return mWillRunDismissFromKeyguard;
+ }
+
+ /**
* Remove any dismiss action or cancel action that was set.
*/
public void cancelDismissAction() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b27cca4..b2f4c30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -153,6 +153,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
import com.android.systemui.util.settings.SecureSettings;
@@ -359,7 +360,7 @@
private final FaceManager mFaceManager;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
- @DevicePostureController.DevicePostureInt
+ @DevicePostureInt
protected int mConfigFaceAuthSupportedPosture;
private KeyguardBypassController mKeyguardBypassController;
@@ -707,6 +708,12 @@
if (mKeyguardGoingAway) {
updateFaceListeningState(BIOMETRIC_ACTION_STOP,
FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardGoingAway();
+ }
+ }
}
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -1846,10 +1853,15 @@
final DevicePostureController.Callback mPostureCallback =
new DevicePostureController.Callback() {
@Override
- public void onPostureChanged(int posture) {
+ public void onPostureChanged(@DevicePostureInt int posture) {
+ boolean currentPostureAllowsFaceAuth = doesPostureAllowFaceAuth(mPostureState);
+ boolean newPostureAllowsFaceAuth = doesPostureAllowFaceAuth(posture);
mPostureState = posture;
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_POSTURE_CHANGED);
+ if (currentPostureAllowsFaceAuth && !newPostureAllowsFaceAuth) {
+ mLogger.d("New posture does not allow face auth, stopping it");
+ updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_UPDATED_POSTURE_CHANGED);
+ }
}
};
@@ -2886,9 +2898,7 @@
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
- final boolean isPostureAllowedForFaceAuth =
- mConfigFaceAuthSupportedPosture == 0 /* DEVICE_POSTURE_UNKNOWN */ ? true
- : (mPostureState == mConfigFaceAuthSupportedPosture);
+ final boolean isPostureAllowedForFaceAuth = doesPostureAllowFaceAuth(mPostureState);
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
@@ -2937,6 +2947,11 @@
return shouldListen;
}
+ private boolean doesPostureAllowFaceAuth(@DevicePostureInt int posture) {
+ return mConfigFaceAuthSupportedPosture == DEVICE_POSTURE_UNKNOWN
+ || (posture == mConfigFaceAuthSupportedPosture);
+ }
+
private void logListenerModelData(@NonNull KeyguardListenModel model) {
mLogger.logKeyguardListenerModel(model);
if (model instanceof KeyguardFingerprintListenModel) {
@@ -3635,7 +3650,9 @@
* Register to receive notifications about general keyguard information
* (see {@link KeyguardUpdateMonitorCallback}.
*
- * @param callback The callback to register
+ * @param callback The callback to register. Stay away from passing anonymous instances
+ * as they will likely be dereferenced. Ensure that the callback is a class
+ * field to persist it.
*/
public void registerCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 0d4889a..feff216 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -317,4 +317,9 @@
* Called when the non-strong biometric state changed.
*/
public void onNonStrongBiometricAllowedChanged(int userId) { }
+
+ /**
+ * Called when keyguard is going away or not going away.
+ */
+ public void onKeyguardGoingAway() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 3555d0a..2d37c29 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -173,12 +173,6 @@
fun removeFavorites(componentName: ComponentName): Boolean
/**
- * Checks if the favorites can be removed. You can't remove components from the preferred list.
- * @param componentName the name of the service that provides the [Control]
- */
- fun canRemoveFavorites(componentName: ComponentName): Boolean
-
- /**
* Replaces the favorites for the given structure.
*
* Calling this method will eliminate the previous selection of favorites and replace it with a
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 8547903..e8c97bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -37,6 +37,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
@@ -55,16 +56,17 @@
@SysUISingleton
class ControlsControllerImpl @Inject constructor (
- private val context: Context,
- @Background private val executor: DelayableExecutor,
- private val uiController: ControlsUiController,
- private val bindingController: ControlsBindingController,
- private val listingController: ControlsListingController,
- private val userFileManager: UserFileManager,
- private val userTracker: UserTracker,
- private val authorizedPanelsRepository: AuthorizedPanelsRepository,
- optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
- dumpManager: DumpManager,
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val selectedComponentRepository: SelectedComponentRepository,
+ private val bindingController: ControlsBindingController,
+ private val listingController: ControlsListingController,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpManager: DumpManager,
) : Dumpable, ControlsController {
companion object {
@@ -497,17 +499,14 @@
}
}
- override fun canRemoveFavorites(componentName: ComponentName): Boolean =
- !authorizedPanelsRepository.getPreferredPackages().contains(componentName.packageName)
-
override fun removeFavorites(componentName: ComponentName): Boolean {
if (!confirmAvailability()) return false
- if (!canRemoveFavorites(componentName)) return false
executor.execute {
- Favorites.removeStructures(componentName)
+ if (Favorites.removeStructures(componentName)) {
+ persistenceWrapper.storeFavorites(Favorites.getAllStructures())
+ }
authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName))
- persistenceWrapper.storeFavorites(Favorites.getAllStructures())
}
return true
}
@@ -574,7 +573,9 @@
}
override fun setPreferredSelection(selectedItem: SelectedItem) {
- uiController.updatePreferences(selectedItem)
+ selectedComponentRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(selectedItem)
+ )
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index d949d11..2af49aa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -36,6 +36,8 @@
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepositoryImpl
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -114,6 +116,11 @@
repository: AuthorizedPanelsRepositoryImpl
): AuthorizedPanelsRepository
+ @Binds
+ abstract fun providePreferredPanelRepository(
+ repository: SelectedComponentRepositoryImpl
+ ): SelectedComponentRepository
+
@BindsOptionalOf
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index e51e832..5c2402b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -20,6 +20,8 @@
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -30,7 +32,8 @@
constructor(
private val context: Context,
private val userFileManager: UserFileManager,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val featureFlags: FeatureFlags,
) : AuthorizedPanelsRepository {
override fun getAuthorizedPanels(): Set<String> {
@@ -71,8 +74,18 @@
userTracker.userId,
)
- // If we've never run this (i.e., the key doesn't exist), add the default packages
- if (sharedPref.getStringSet(KEY, null) == null) {
+ // We should add default packages in two cases:
+ // 1) We've never run this
+ // 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps
+ val needToSetup =
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+ sharedPref.getStringSet(KEY, null) == null
+ } else {
+ // There might be an empty set that need to be overridden after the feature has been
+ // turned off after being turned on
+ sharedPref.getStringSet(KEY, null).isNullOrEmpty()
+ }
+ if (needToSetup) {
sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
}
return sharedPref
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
new file mode 100644
index 0000000..5bb6eec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+import android.content.ComponentName
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.flags.Flags
+
+/** Stores user-selected preferred component. */
+interface SelectedComponentRepository {
+
+ /**
+ * Returns currently set preferred component, or null when nothing is set. Consider using
+ * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
+ */
+ fun getSelectedComponent(): SelectedComponent?
+
+ /** Sets preferred component. Use [getSelectedComponent] to get current one */
+ fun setSelectedComponent(selectedComponent: SelectedComponent)
+
+ /** Clears current preferred component. [getSelectedComponent] will return null afterwards */
+ fun removeSelectedComponent()
+
+ /**
+ * Return true when default preferred component should be set up and false the otherwise. This
+ * is always true when [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
+ */
+ fun shouldAddDefaultComponent(): Boolean
+
+ /**
+ * Sets if default component should be added. This is ignored when
+ * [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
+ */
+ fun setShouldAddDefaultComponent(shouldAdd: Boolean)
+
+ data class SelectedComponent(
+ val name: String,
+ val componentName: ComponentName?,
+ val isPanel: Boolean,
+ ) {
+ constructor(
+ selectedItem: SelectedItem
+ ) : this(
+ name = selectedItem.name.toString(),
+ componentName = selectedItem.componentName,
+ isPanel = selectedItem is SelectedItem.PanelItem,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
new file mode 100644
index 0000000..0fb5b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import javax.inject.Inject
+
+@SysUISingleton
+class SelectedComponentRepositoryImpl
+@Inject
+constructor(
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ private val featureFlags: FeatureFlags,
+) : SelectedComponentRepository {
+
+ private companion object {
+ const val PREF_COMPONENT = "controls_component"
+ const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+ const val PREF_IS_PANEL = "controls_is_panel"
+ const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
+ }
+
+ private val sharedPreferences: SharedPreferences
+ get() =
+ userFileManager.getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = Context.MODE_PRIVATE,
+ userId = userTracker.userId
+ )
+
+ override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
+ with(sharedPreferences) {
+ val componentString = getString(PREF_COMPONENT, null) ?: return null
+ return SelectedComponentRepository.SelectedComponent(
+ name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
+ componentName = ComponentName.unflattenFromString(componentString),
+ isPanel = getBoolean(PREF_IS_PANEL, false)
+ )
+ }
+ }
+
+ override fun setSelectedComponent(
+ selectedComponent: SelectedComponentRepository.SelectedComponent
+ ) {
+ sharedPreferences
+ .edit()
+ .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
+ .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
+ .putBoolean(PREF_IS_PANEL, selectedComponent.isPanel)
+ .apply()
+ }
+
+ override fun removeSelectedComponent() {
+ sharedPreferences
+ .edit()
+ .remove(PREF_COMPONENT)
+ .remove(PREF_STRUCTURE_OR_APP_NAME)
+ .remove(PREF_IS_PANEL)
+ .apply()
+ }
+
+ override fun shouldAddDefaultComponent(): Boolean =
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+ sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
+ } else {
+ true
+ }
+
+ override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
+ sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 9d99253..3a4a00c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -18,17 +18,16 @@
package com.android.systemui.controls.start
import android.content.Context
-import android.content.res.Resources
import android.os.UserHandle
import com.android.systemui.CoreStartable
-import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -37,7 +36,7 @@
* Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
*
* In particular, it will perform the following:
- * * If there is no preferred selection for provider and at least one of the preferred packages
+ * * If there is no preferred selection for provider and at least one of the preferred packages
* provides a panel, it will select the first one that does.
* * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
* displaying the panel).
@@ -48,10 +47,11 @@
class ControlsStartable
@Inject
constructor(
- @Main private val resources: Resources,
- @Background private val executor: Executor,
- private val controlsComponent: ControlsComponent,
- private val userTracker: UserTracker
+ @Background private val executor: Executor,
+ private val controlsComponent: ControlsComponent,
+ private val userTracker: UserTracker,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ private val selectedComponentRepository: SelectedComponentRepository,
) : CoreStartable {
// These two controllers can only be accessed after `start` method once we've checked if the
@@ -85,12 +85,15 @@
}
private fun selectDefaultPanelIfNecessary() {
+ if (!selectedComponentRepository.shouldAddDefaultComponent()) {
+ return
+ }
val currentSelection = controlsController.getPreferredSelection()
if (currentSelection == SelectedItem.EMPTY_SELECTION) {
val availableServices = controlsListingController.getCurrentServices()
val panels = availableServices.filter { it.panelActivity != null }
- resources
- .getStringArray(R.array.config_controlsPreferredPackages)
+ authorizedPanelsRepository
+ .getPreferredPackages()
// Looking for the first element in the string array such that there is one package
// that has a panel. It will return null if there are no packages in the array,
// or if no packages in the array have a panel associated with it.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 58673bb..0d53117 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -64,8 +64,6 @@
* This element will be the one that appears when the user first opens the controls activity.
*/
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
-
- fun updatePreferences(selectedItem: SelectedItem)
}
sealed class SelectedItem {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index c61dad6..5da86de9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -64,6 +64,7 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -73,9 +74,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.asIndenting
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -84,7 +83,7 @@
import dagger.Lazy
import java.io.PrintWriter
import java.text.Collator
-import java.util.*
+import java.util.Optional
import java.util.function.Consumer
import javax.inject.Inject
@@ -98,25 +97,22 @@
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
- val controlActionCoordinator: ControlActionCoordinator,
+ private val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val keyguardStateController: KeyguardStateController,
- private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ private val selectedComponentRepository: SelectedComponentRepository,
private val featureFlags: FeatureFlags,
private val dialogsFactory: ControlsDialogsFactory,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
companion object {
- private const val PREF_COMPONENT = "controls_component"
- private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
- private const val PREF_IS_PANEL = "controls_is_panel"
private const val FADE_IN_MILLIS = 200L
@@ -138,12 +134,6 @@
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
private var lastSelections = emptyList<SelectionItem>()
- private val sharedPreferences
- get() = userFileManager.getSharedPreferences(
- fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- mode = 0,
- userId = userTracker.userId
- )
private var taskViewController: PanelTaskViewController? = null
@@ -341,20 +331,18 @@
if (!controlsController.get().removeFavorites(componentName)) {
return@createRemoveAppDialog
}
- if (
- sharedPreferences.getString(PREF_COMPONENT, "") ==
- componentName.flattenToString()
- ) {
- sharedPreferences
- .edit()
- .remove(PREF_COMPONENT)
- .remove(PREF_STRUCTURE_OR_APP_NAME)
- .remove(PREF_IS_PANEL)
- .commit()
+
+ if (selectedComponentRepository.getSelectedComponent()?.componentName ==
+ componentName) {
+ selectedComponentRepository.removeSelectedComponent()
}
- allStructures = controlsController.get().getFavorites()
- selectedItem = getPreferredSelectedItem(allStructures)
+ val selectedItem = getPreferredSelectedItem(controlsController.get().getFavorites())
+ if (selectedItem == SelectedItem.EMPTY_SELECTION) {
+ // User removed the last panel. In this case we start app selection flow and don't
+ // want to auto-add it again
+ selectedComponentRepository.setShouldAddDefaultComponent(false)
+ }
reload(parent)
}.apply { show() }
}
@@ -522,8 +510,7 @@
ADD_APP_ID
))
}
- if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) &&
- controlsController.get().canRemoveFavorites(selectedItem.componentName)) {
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
add(OverflowMenuAdapter.MenuItem(
context.getText(R.string.controls_menu_remove),
REMOVE_APP_ID,
@@ -569,7 +556,7 @@
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
REMOVE_APP_ID -> startRemovingApp(
- selectedStructure.componentName, selectionItem.appName
+ selectionItem.componentName, selectionItem.appName
)
}
dismiss()
@@ -714,29 +701,22 @@
}
override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
- val sp = sharedPreferences
-
- val component = sp.getString(PREF_COMPONENT, null)?.let {
- ComponentName.unflattenFromString(it)
- } ?: EMPTY_COMPONENT
- val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
- val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
- return if (isPanel) {
- SelectedItem.PanelItem(name, component)
+ val preferredPanel = selectedComponentRepository.getSelectedComponent()
+ val component = preferredPanel?.componentName ?: EMPTY_COMPONENT
+ return if (preferredPanel?.isPanel == true) {
+ SelectedItem.PanelItem(preferredPanel.name, component)
} else {
if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
SelectedItem.StructureItem(structures.firstOrNull {
- component == it.componentName && name == it.structure
- } ?: structures.get(0))
+ component == it.componentName && preferredPanel?.name == it.structure
+ } ?: structures[0])
}
}
- override fun updatePreferences(selectedItem: SelectedItem) {
- sharedPreferences.edit()
- .putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
- .putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
- .putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
- .apply()
+ private fun updatePreferences(selectedItem: SelectedItem) {
+ selectedComponentRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(selectedItem)
+ )
}
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cedc226a..05527bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -70,6 +70,8 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -306,4 +308,8 @@
@Binds
abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
+
+ @Binds
+ abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
+ LargeScreenShadeInterpolatorImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ca1cef3..d0a92f0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -43,7 +43,6 @@
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
@@ -131,9 +130,17 @@
}
}
- /** Starts the dream content and dream overlay entry animations. */
+ /**
+ * Starts the dream content and dream overlay entry animations.
+ *
+ * @param downwards if true, the entry animation translations downwards into position rather
+ * than upwards.
+ */
@JvmOverloads
- fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ fun startEntryAnimations(
+ downwards: Boolean,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
cancelAnimations()
mAnimator =
@@ -153,7 +160,7 @@
interpolator = Interpolators.LINEAR
),
translationYAnimator(
- from = mDreamInTranslationYDistance.toFloat(),
+ from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1),
to = 0f,
durationMs = mDreamInTranslationYDurationMs,
interpolator = Interpolators.EMPHASIZED_DECELERATE
@@ -167,6 +174,71 @@
}
}
+ /**
+ * Starts the dream content and dream overlay exit animations.
+ *
+ * This should only be used when the low light dream is entering, animations to/from other SysUI
+ * views is controlled by `transitionViewModel`.
+ */
+ // TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is
+ // done.
+ @JvmOverloads
+ fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ translationYAnimator(
+ from = 0f,
+ to = -mDreamInTranslationYDistance.toFloat(),
+ durationMs = mDreamInTranslationYDurationMs,
+ delayMs = 0,
+ interpolator = Interpolators.EMPHASIZED
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_BOTTOM
+ )
+ .apply {
+ doOnEnd {
+ // The logical end of the animation is once the alpha and blur
+ // animations finish, end the animation so that any listeners are
+ // notified. The Y translation animation is much longer than all of
+ // the other animations due to how the spec is defined, but is not
+ // expected to run to completion.
+ mAnimator?.end()
+ }
+ },
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
+ return mAnimator as AnimatorSet
+ }
+
/** Starts the dream content and dream overlay exit animations. */
fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations()
@@ -182,19 +254,6 @@
}
}
- /**
- * Ends the dream content and dream overlay animations, if they're currently running.
- *
- * @see [AnimatorSet.end]
- */
- fun endAnimations() {
- mAnimator =
- mAnimator?.let {
- it.end()
- null
- }
- }
-
private fun blurAnimator(
view: View,
fromBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 50cfb6a..4b478cd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -23,6 +23,7 @@
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
+import android.animation.Animator;
import android.content.res.Resources;
import android.os.Handler;
import android.util.MathUtils;
@@ -31,6 +32,7 @@
import androidx.annotation.NonNull;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,11 +56,14 @@
* View controller for {@link DreamOverlayContainerView}.
*/
@DreamOverlayComponent.DreamOverlayScope
-public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
+public class DreamOverlayContainerViewController extends
+ ViewController<DreamOverlayContainerView> implements
+ LowLightTransitionCoordinator.LowLightEnterListener {
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final BlurUtils mBlurUtils;
private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
private final DreamOverlayStateController mStateController;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -143,19 +148,18 @@
};
/**
- * If true, overlay entry animations should be skipped once.
- *
- * This is turned on when exiting low light and should be turned off once the entry animations
- * are skipped once.
+ * If {@code true}, the dream has just transitioned from the low light dream back to the user
+ * dream and we should play an entry animation where the overlay slides in downwards from the
+ * top instead of the typicla slide in upwards from the bottom.
*/
- private boolean mSkipEntryAnimations;
+ private boolean mExitingLowLight;
private final DreamOverlayStateController.Callback
mDreamOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@Override
public void onExitLowLight() {
- mSkipEntryAnimations = true;
+ mExitingLowLight = true;
}
};
@@ -165,6 +169,7 @@
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils,
@Main Handler handler,
@Main Resources resources,
@@ -182,6 +187,7 @@
mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController;
mStateController = stateController;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
@@ -208,6 +214,7 @@
mStatusBarViewController.init();
mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
+ mLowLightTransitionCoordinator.setLowLightEnterListener(this);
}
@Override
@@ -219,14 +226,10 @@
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
- mDreamOverlayAnimationsController.startEntryAnimations();
-
- if (mSkipEntryAnimations) {
- // If we're transitioning from the low light dream back to the user dream, skip the
- // overlay animations and show immediately.
- mDreamOverlayAnimationsController.endAnimations();
- mSkipEntryAnimations = false;
- }
+ // If this is transitioning from the low light dream to the user dream, the overlay
+ // should translate in downwards instead of upwards.
+ mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight);
+ mExitingLowLight = false;
}
}
@@ -310,4 +313,12 @@
mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
}
+
+ @Override
+ public Animator onBeforeEnterLowLight() {
+ // Return the animator so that the transition coordinator waits for the overlay exit
+ // animations to finish before entering low light, as otherwise the default DreamActivity
+ // animation plays immediately and there's no time for this animation to play.
+ return mDreamOverlayAnimationsController.startExitAnimations();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
new file mode 100644
index 0000000..b20e33a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Restarts the process after all passed in [Condition]s are true. */
+class ConditionalRestarter
+@Inject
+constructor(
+ private val systemExitRestarter: SystemExitRestarter,
+ private val conditions: Set<@JvmSuppressWildcards Condition>,
+ @Named(RESTART_DELAY) private val restartDelaySec: Long,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : Restarter {
+
+ private var restartJob: Job? = null
+ private var pendingReason = ""
+ private var androidRestartRequested = false
+
+ override fun restartSystemUI(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
+ scheduleRestart(reason)
+ }
+
+ override fun restartAndroid(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.")
+ androidRestartRequested = true
+ scheduleRestart(reason)
+ }
+
+ private fun scheduleRestart(reason: String = "") {
+ pendingReason = if (reason.isEmpty()) pendingReason else reason
+
+ if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
+ if (restartJob == null) {
+ restartJob =
+ applicationScope.launch(backgroundDispatcher) {
+ delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
+ restartNow()
+ }
+ }
+ } else {
+ restartJob?.cancel()
+ restartJob = null
+ }
+ }
+
+ private fun restartNow() {
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid(pendingReason)
+ } else {
+ systemExitRestarter.restartSystemUI(pendingReason)
+ }
+ }
+
+ interface Condition {
+ /**
+ * Should return true if the system is ready to restart.
+ *
+ * A call to this function means that we want to restart and are waiting for this condition
+ * to return true.
+ *
+ * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_
+ * ready to restart. At that point, this method will be called again to verify that the
+ * system is ready.
+ *
+ * Multiple calls to an instance of this method may happen for a single restart attempt if
+ * multiple [Condition]s are being checked. If any one [Condition] returns false, all the
+ * [Condition]s will need to be rechecked on the next restart attempt.
+ */
+ fun canRestartNow(retryFn: () -> Unit): Boolean
+ }
+
+ companion object {
+ const val RESTART_DELAY = "restarter_restart_delay"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
deleted file mode 100644
index a6956a4..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import javax.inject.Inject
-
-/** Restarts SystemUI when the screen is locked. */
-class FeatureFlagsDebugRestarter
-@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val systemExitRestarter: SystemExitRestarter,
-) : Restarter {
-
- private var androidRestartRequested = false
- private var pendingReason = ""
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
- restartNow()
- }
- }
-
- override fun restartSystemUI(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
- Log.i(FeatureFlagsDebug.TAG, reason)
- scheduleRestart(reason)
- }
-
- override fun restartAndroid(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
- androidRestartRequested = true
- scheduleRestart(reason)
- }
-
- fun scheduleRestart(reason: String) {
- pendingReason = reason
- if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
- restartNow()
- } else {
- wakefulnessLifecycle.addObserver(observer)
- }
- }
-
- private fun restartNow() {
- if (androidRestartRequested) {
- systemExitRestarter.restartAndroid(pendingReason)
- } else {
- systemExitRestarter.restartSystemUI(pendingReason)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
deleted file mode 100644
index c08266c..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-/** Restarts SystemUI when the device appears idle. */
-class FeatureFlagsReleaseRestarter
-@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val batteryController: BatteryController,
- @Background private val bgExecutor: DelayableExecutor,
- private val systemExitRestarter: SystemExitRestarter
-) : Restarter {
- var listenersAdded = false
- var pendingRestart: Runnable? = null
- private var pendingReason = ""
- var androidRestartRequested = false
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- scheduleRestart(pendingReason)
- }
- }
-
- val batteryCallback =
- object : BatteryController.BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- scheduleRestart(pendingReason)
- }
- }
-
- override fun restartSystemUI(reason: String) {
- Log.d(
- FeatureFlagsDebug.TAG,
- "SystemUI Restart requested. Restarting when plugged in and idle."
- )
- scheduleRestart(reason)
- }
-
- override fun restartAndroid(reason: String) {
- Log.d(
- FeatureFlagsDebug.TAG,
- "Android Restart requested. Restarting when plugged in and idle."
- )
- androidRestartRequested = true
- scheduleRestart(reason)
- }
-
- private fun scheduleRestart(reason: String) {
- // Don't bother adding listeners twice.
- pendingReason = reason
- if (!listenersAdded) {
- listenersAdded = true
- wakefulnessLifecycle.addObserver(observer)
- batteryController.addCallback(batteryCallback)
- }
- if (
- wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
- ) {
- if (pendingRestart == null) {
- pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
- }
- } else if (pendingRestart != null) {
- pendingRestart?.run()
- pendingRestart = null
- }
- }
-
- private fun restartNow() {
- Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
- if (androidRestartRequested) {
- systemExitRestarter.restartAndroid(pendingReason)
- } else {
- systemExitRestarter.restartSystemUI(pendingReason)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6d0d893..03c98d2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -117,6 +117,9 @@
val ANIMATED_NOTIFICATION_SHADE_INSETS =
unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+ // TODO(b/268005230): Tracking Bug
+ @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -388,13 +391,16 @@
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
// TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2")
+ val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@JvmField
- val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(1004, "enable_low_light_clock_undocked")
+ // TODO(b/271428141): Tracking Bug
+ val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(
+ 1004,
+ "enable_low_light_clock_undocked", teamfood = true)
// 1100 - windowing
@Keep
@@ -482,6 +488,13 @@
val ENABLE_PIP_APP_ICON_OVERLAY =
sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+ // TODO(b/272110828): Tracking bug
+ @Keep
+ @JvmField
+ val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ sysPropBooleanFlag(
+ 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
@@ -590,7 +603,7 @@
@JvmField
val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport")
// TODO(b/265944639): Tracking Bug
- @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
+ @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
// 1900
@JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
@@ -662,4 +675,9 @@
// TODO(b/259428678): Tracking Bug
@JvmField
val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
+
+ // TODO(b/272036292): Tracking Bug
+ @JvmField
+ val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
+ unreleasedFlag(2602, "large_shade_granular_alpha_interpolation")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 0054d26..3c50125 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.flags
+import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -22,6 +23,8 @@
/** Module containing shared code for all FeatureFlag implementations. */
@Module
interface FlagsCommonModule {
+ @Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter
+
companion object {
const val ALL_FLAGS = "all_flags"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
new file mode 100644
index 0000000..3120638
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Returns true when the device is plugged in. */
+class PluggedInCondition
+@Inject
+constructor(
+ private val batteryController: BatteryController,
+) : ConditionalRestarter.Condition {
+
+ var listenersAdded = false
+ var retryFn: (() -> Unit)? = null
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ retryFn?.invoke()
+ }
+ }
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ if (!listenersAdded) {
+ listenersAdded = true
+ batteryController.addCallback(batteryCallback)
+ }
+
+ this.retryFn = retryFn
+
+ return batteryController.isPluggedIn
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
new file mode 100644
index 0000000..49e61af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class ScreenIdleCondition
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+) : ConditionalRestarter.Condition {
+
+ var listenersAdded = false
+ var retryFn: (() -> Unit)? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ retryFn?.invoke()
+ }
+ }
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ if (!listenersAdded) {
+ listenersAdded = true
+ wakefulnessLifecycle.addObserver(observer)
+ }
+
+ this.retryFn = retryFn
+
+ return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
index 85d0379..5e806b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
@@ -46,8 +46,15 @@
viewModel.dialogContent.collect { dialogViewModel ->
if (dialogViewModel != null) {
if (dialog == null) {
- dialog = KeyboardBacklightDialog(context, dialogViewModel)
- // pass viewModel and show
+ dialog =
+ KeyboardBacklightDialog(
+ context,
+ initialCurrentLevel = dialogViewModel.currentValue,
+ initialMaxLevel = dialogViewModel.maxValue
+ )
+ dialog?.show()
+ } else {
+ dialog?.updateState(dialogViewModel.currentValue, dialogViewModel.maxValue)
}
} else {
dialog?.dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index b68a2a8..a173f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,16 +17,260 @@
package com.android.systemui.keyboard.backlight.ui.view
+import android.annotation.ColorInt
import android.app.Dialog
import android.content.Context
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
import android.os.Bundle
-import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogContentViewModel
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.LayoutParams
+import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.systemui.R
+import com.android.systemui.util.children
-class KeyboardBacklightDialog(context: Context, val viewModel: BacklightDialogContentViewModel) :
- Dialog(context) {
+class KeyboardBacklightDialog(
+ context: Context,
+ initialCurrentLevel: Int,
+ initialMaxLevel: Int,
+) : Dialog(context) {
+
+ private data class RootProperties(
+ val cornerRadius: Float,
+ val verticalPadding: Int,
+ val horizontalPadding: Int,
+ )
+
+ private data class BacklightIconProperties(
+ val width: Int,
+ val height: Int,
+ val leftMargin: Int,
+ )
+
+ private data class StepViewProperties(
+ val width: Int,
+ val height: Int,
+ val horizontalMargin: Int,
+ val smallRadius: Float,
+ val largeRadius: Float,
+ )
+
+ private var currentLevel: Int = 0
+ private var maxLevel: Int = 0
+
+ private lateinit var rootView: LinearLayout
+
+ private var dialogBottomMargin = 208
+ private lateinit var rootProperties: RootProperties
+ private lateinit var iconProperties: BacklightIconProperties
+ private lateinit var stepProperties: StepViewProperties
+ @ColorInt var filledRectangleColor: Int = 0
+ @ColorInt var emptyRectangleColor: Int = 0
+ @ColorInt var backgroundColor: Int = 0
+
+ init {
+ currentLevel = initialCurrentLevel
+ maxLevel = initialMaxLevel
+ }
override fun onCreate(savedInstanceState: Bundle?) {
+ setUpWindowProperties(this)
+ setWindowTitle()
+ updateResources()
+ rootView = buildRootView()
+ setContentView(rootView)
super.onCreate(savedInstanceState)
- // TODO(b/268650355) Implement the dialog
+ updateState(currentLevel, maxLevel, forceRefresh = true)
+ }
+
+ private fun updateResources() {
+ context.resources.apply {
+ filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
+ emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
+ backgroundColor = getColor(R.color.backlight_indicator_background)
+ rootProperties =
+ RootProperties(
+ cornerRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_corner_radius)
+ .toFloat(),
+ verticalPadding =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_vertical_padding),
+ horizontalPadding =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_horizontal_padding)
+ )
+ iconProperties =
+ BacklightIconProperties(
+ width = getDimensionPixelSize(R.dimen.backlight_indicator_icon_width),
+ height = getDimensionPixelSize(R.dimen.backlight_indicator_icon_height),
+ leftMargin =
+ getDimensionPixelSize(R.dimen.backlight_indicator_icon_left_margin),
+ )
+ stepProperties =
+ StepViewProperties(
+ width = getDimensionPixelSize(R.dimen.backlight_indicator_step_width),
+ height = getDimensionPixelSize(R.dimen.backlight_indicator_step_height),
+ horizontalMargin =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_horizontal_margin),
+ smallRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_small_radius)
+ .toFloat(),
+ largeRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_large_radius)
+ .toFloat(),
+ )
+ }
+ }
+
+ fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
+ if (maxLevel != max || forceRefresh) {
+ maxLevel = max
+ rootView.removeAllViews()
+ buildStepViews().forEach { rootView.addView(it) }
+ }
+ currentLevel = current
+ updateLevel()
+ }
+
+ private fun updateLevel() {
+ rootView.children.forEachIndexed(
+ action = { index, v ->
+ val drawable = v.background as ShapeDrawable
+ if (index <= currentLevel) {
+ updateColor(drawable, filledRectangleColor)
+ } else {
+ updateColor(drawable, emptyRectangleColor)
+ }
+ }
+ )
+ }
+
+ private fun updateColor(drawable: ShapeDrawable, @ColorInt color: Int) {
+ if (drawable.paint.color != color) {
+ drawable.paint.color = color
+ drawable.invalidateSelf()
+ }
+ }
+
+ private fun buildRootView(): LinearLayout {
+ val linearLayout =
+ LinearLayout(context).apply {
+ orientation = LinearLayout.HORIZONTAL
+ layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+ setPadding(
+ /* left= */ rootProperties.horizontalPadding,
+ /* top= */ rootProperties.verticalPadding,
+ /* right= */ rootProperties.horizontalPadding,
+ /* bottom= */ rootProperties.verticalPadding
+ )
+ }
+ val drawable =
+ ShapeDrawable(
+ RoundRectShape(
+ /* outerRadii= */ FloatArray(8) { rootProperties.cornerRadius },
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ )
+ drawable.paint.color = backgroundColor
+ linearLayout.background = drawable
+ return linearLayout
+ }
+
+ private fun buildStepViews(): List<FrameLayout> {
+ val stepViews = (0..maxLevel).map { i -> createStepViewAt(i) }
+ stepViews[0].addView(createBacklightIconView())
+ return stepViews
+ }
+
+ private fun createStepViewAt(i: Int): FrameLayout {
+ return FrameLayout(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(stepProperties.width, stepProperties.height).apply {
+ setMargins(
+ /* left= */ stepProperties.horizontalMargin,
+ /* top= */ 0,
+ /* right= */ stepProperties.horizontalMargin,
+ /* bottom= */ 0
+ )
+ }
+ val drawable =
+ ShapeDrawable(
+ RoundRectShape(
+ /* outerRadii= */ radiiForIndex(i, maxLevel),
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ )
+ drawable.paint.color = emptyRectangleColor
+ background = drawable
+ }
+ }
+
+ private fun createBacklightIconView(): ImageView {
+ return ImageView(context).apply {
+ setImageResource(R.drawable.ic_keyboard_backlight)
+ layoutParams =
+ FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
+ gravity = Gravity.CENTER
+ leftMargin = iconProperties.leftMargin
+ }
+ }
+ }
+
+ private fun setWindowTitle() {
+ val attrs = window.attributes
+ // TODO(b/271796169): check if title needs to be a translatable resource.
+ attrs.title = "KeyboardBacklightDialog"
+ attrs?.y = dialogBottomMargin
+ window.attributes = attrs
+ }
+
+ private fun setUpWindowProperties(dialog: Dialog) {
+ val window = dialog.window
+ window.requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ )
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ window.setBackgroundDrawableResource(android.R.color.transparent)
+ window.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+ setCanceledOnTouchOutside(true)
+ }
+
+ private fun radiiForIndex(i: Int, last: Int): FloatArray {
+ val smallRadius = stepProperties.smallRadius
+ val largeRadius = stepProperties.largeRadius
+ return when (i) {
+ 0 -> // left radii bigger
+ floatArrayOf(
+ largeRadius,
+ largeRadius,
+ smallRadius,
+ smallRadius,
+ smallRadius,
+ smallRadius,
+ largeRadius,
+ largeRadius
+ )
+ last -> // right radii bigger
+ floatArrayOf(
+ smallRadius,
+ smallRadius,
+ largeRadius,
+ largeRadius,
+ largeRadius,
+ largeRadius,
+ smallRadius,
+ smallRadius
+ )
+ else -> FloatArray(8) { smallRadius } // all radii equal
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 85554ac..2815df6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -964,13 +964,24 @@
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ if (!handleOnAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback)) {
+ // Usually we rely on animation completion to synchronize occluded status,
+ // but there was no animation to play, so just update it now.
+ setOccluded(true /* isOccluded */, false /* animate */);
+ }
+ }
+
+ private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
if (apps == null || apps.length == 0 || apps[0] == null) {
if (DEBUG) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+ "skipping occluding animation.");
}
finishedCallback.onAnimationFinished();
- return;
+ return false;
}
final RemoteAnimationTarget primary = apps[0];
@@ -980,7 +991,7 @@
Log.w(TAG, "The occluding app isn't Dream; "
+ "finishing up. Please check that the config is correct.");
finishedCallback.onAnimationFinished();
- return;
+ return false;
}
final SyncRtSurfaceTransactionApplier applier =
@@ -1029,6 +1040,7 @@
mOccludeByDreamAnimator.start();
});
+ return true;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index faeb485..a2589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -52,6 +52,7 @@
cancelAction: Runnable?,
)
fun willDismissWithActions(): Boolean
+ fun willRunDismissFromKeyguard(): Boolean
/** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
fun getBackCallback(): OnBackAnimationCallback
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2dc8fee..1fbfff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -47,6 +47,7 @@
listenForOccludedToLockscreen()
listenForOccludedToDreaming()
listenForOccludedToAodOrDozing()
+ listenForOccludedToGone()
}
private fun listenForOccludedToDreaming() {
@@ -72,11 +73,22 @@
private fun listenForOccludedToLockscreen() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
- .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { (isOccluded, lastStartedKeyguardState) ->
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
- if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
+ if (
+ !isOccluded &&
+ isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -90,6 +102,38 @@
}
}
+ private fun listenForOccludedToGone() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (
+ !isOccluded &&
+ !isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.OCCLUDED,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun listenForOccludedToAodOrDozing() {
scope.launch {
keyguardInteractor.wakefulnessModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ec99049..c42e502 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -142,6 +142,8 @@
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /** Whether or not quick settings or quick quick settings are showing. */
+ val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
/**
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
* side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 568cc0f..66f87ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -93,8 +93,9 @@
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
- ) { affordance, isDozing, isKeyguardShowing ->
- if (!isDozing && isKeyguardShowing) {
+ keyguardInteractor.isQuickSettingsVisible
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible ->
+ if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index c709fd1..a263562 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -122,21 +122,24 @@
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
- init {
- keyguardUpdateMonitor.registerCallback(
- object : KeyguardUpdateMonitorCallback() {
- override fun onBiometricRunningStateChanged(
- running: Boolean,
- biometricSourceType: BiometricSourceType?
- ) {
- updateSideFpsVisibility()
- }
+ /**
+ * This callback needs to be a class field so it does not get garbage collected.
+ */
+ val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricRunningStateChanged(
+ running: Boolean,
+ biometricSourceType: BiometricSourceType?
+ ) {
+ updateSideFpsVisibility()
+ }
- override fun onStrongAuthStateChanged(userId: Int) {
- updateSideFpsVisibility()
- }
- }
- )
+ override fun onStrongAuthStateChanged(userId: Int) {
+ updateSideFpsVisibility()
+ }
+ }
+
+ init {
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
}
// TODO(b/243685699): Move isScrimmed logic to data layer.
@@ -377,6 +380,11 @@
return primaryBouncerView.delegate?.willDismissWithActions() == true
}
+ /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
+ fun willRunDismissFromKeyguard(): Boolean {
+ return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
+ }
+
/** Returns whether the bouncer should be full screen. */
private fun needsFullscreenBouncer(): Boolean {
val mode: KeyguardSecurityModel.SecurityMode =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
new file mode 100644
index 0000000..1db7733
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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
+
+/** Alpha values for scrim updates */
+data class ScrimAlpha(
+ val frontAlpha: Float = 0f,
+ val behindAlpha: Float = 0f,
+ val notificationsAlpha: 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
index 2337ffc..bb617bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -97,6 +97,10 @@
override fun willDismissWithActions(): Boolean {
return securityContainerController.hasDismissActions()
}
+
+ override fun willRunDismissFromKeyguard(): Boolean {
+ return securityContainerController.willRunDismissFromKeyguard()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 92038e2..b23247c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
/**
* Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -36,6 +39,7 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
private val statusBarStateController: SysuiStatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
@@ -44,26 +48,49 @@
)
private var leaveShadeOpen: Boolean = false
+ private var willRunDismissFromKeyguard: Boolean = false
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
transitionAnimation.createFlow(
duration = 200.milliseconds,
- onStep = { 1f - it },
- )
-
- /** Scrim behind alpha */
- val scrimBehindAlpha: Flow<Float> =
- transitionAnimation.createFlow(
- duration = TO_GONE_DURATION,
- interpolator = EMPHASIZED_ACCELERATE,
- onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() },
+ onStart = {
+ willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
onStep = {
- if (leaveShadeOpen) {
- 1f
+ if (willRunDismissFromKeyguard) {
+ 0f
} else {
1f - it
}
},
)
+
+ /** Scrim alpha values */
+ val scrimAlpha: Flow<ScrimAlpha> =
+ transitionAnimation
+ .createFlow(
+ duration = TO_GONE_DURATION,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard =
+ primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
+ onStep = { 1f - it },
+ )
+ .map {
+ if (willRunDismissFromKeyguard) {
+ ScrimAlpha(
+ notificationsAlpha = 1f,
+ )
+ } else if (leaveShadeOpen) {
+ ScrimAlpha(
+ behindAlpha = 1f,
+ notificationsAlpha = 1f,
+ )
+ } else {
+ ScrimAlpha(behindAlpha = it)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6023bc2..525b2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1343,13 +1343,9 @@
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
- val isEligibleForResume =
- removed.isLocalSession() ||
- (mediaFlags.isRemoteResumeAllowed() &&
- removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
- } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) {
+ } else if (isAbleToResume(removed)) {
convertToResumePlayer(key, removed)
} else if (mediaFlags.isRetainingPlayersEnabled()) {
handlePossibleRemoval(key, removed, notificationRemoved = true)
@@ -1369,6 +1365,14 @@
handlePossibleRemoval(key, updated)
}
+ private fun isAbleToResume(data: MediaData): Boolean {
+ val isEligibleForResume =
+ data.isLocalSession() ||
+ (mediaFlags.isRemoteResumeAllowed() &&
+ data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
+ return useMediaResumption && data.resumeAction != null && isEligibleForResume
+ }
+
/**
* Convert to resume state if the player is no longer valid and active, then notify listeners
* that the data was updated. Does not convert to resume state if the player is still valid, or
@@ -1391,8 +1395,9 @@
if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
mediaEntries.put(key, removed)
notifyMediaDataLoaded(key, key, removed)
- } else if (removed.active) {
- // This player was still active - it didn't last long enough to time out: remove
+ } else if (removed.active && !isAbleToResume(removed)) {
+ // This player was still active - it didn't last long enough to time out,
+ // and its app doesn't normally support resume: remove
if (DEBUG) Log.d(TAG, "Removing still-active player $key")
notifyMediaDataRemoved(key)
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 92e0c85..b0389b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -239,6 +239,8 @@
data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) {
// TODO also check for a media button receiver intended for restarting (b/154127084)
+ // Set null action to prevent additional attempts to connect
+ mediaDataManager.setResumeAction(key, null)
Log.d(TAG, "Checking for service component for " + data.packageName)
val pm = context.packageManager
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
@@ -249,9 +251,6 @@
backgroundExecutor.execute {
tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
}
- } else {
- // No service found
- mediaDataManager.setResumeAction(key, null)
}
}
}
@@ -263,8 +262,6 @@
*/
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
Log.d(TAG, "Testing if we can connect to $componentName")
- // Set null action to prevent additional attempts to connect
- mediaDataManager.setResumeAction(key, null)
mediaBrowser =
mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index 3493b24..d460b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -85,16 +85,13 @@
* ResumeMediaBrowser#disconnect will be called automatically with this function.
*/
public void findRecentMedia() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(
+ MediaBrowser browser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "findRecentMedia");
- mMediaBrowser.connect();
+ connectBrowser(browser, "findRecentMedia");
}
private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
@@ -202,6 +199,21 @@
};
/**
+ * Connect using a new media browser. Disconnects the existing browser first, if it exists.
+ * @param browser media browser to connect
+ * @param reason Reason to log for connection
+ */
+ private void connectBrowser(MediaBrowser browser, String reason) {
+ mLogger.logConnection(mComponentName, reason);
+ disconnect();
+ mMediaBrowser = browser;
+ if (browser != null) {
+ browser.connect();
+ }
+ updateMediaController();
+ }
+
+ /**
* Disconnect the media browser. This should be done after callbacks have completed to
* disconnect from the media browser service.
*/
@@ -222,10 +234,9 @@
* getting a media update from the app
*/
public void restart() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(mComponentName,
+ MediaBrowser browser = mBrowserFactory.create(mComponentName,
new MediaBrowser.ConnectionCallback() {
@Override
public void onConnected() {
@@ -265,9 +276,7 @@
disconnect();
}
}, rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "restart");
- mMediaBrowser.connect();
+ connectBrowser(browser, "restart");
}
@VisibleForTesting
@@ -305,16 +314,13 @@
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
*/
public void testConnection() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(
+ MediaBrowser browser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "testConnection");
- mMediaBrowser.connect();
+ connectBrowser(browser, "testConnection");
}
/** Updates mMediaController based on our current browser values. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 9d34df8..938a9c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -51,6 +51,8 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
@@ -60,6 +62,7 @@
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -112,6 +115,8 @@
private final MediaHost mQqsMediaHost;
private final QSFragmentComponent.Factory mQsComponentFactory;
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
private final QSLogger mLogger;
private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
@@ -159,12 +164,7 @@
// visible;
private boolean mQsVisible;
- /**
- * Whether the notification panel uses the full width of the screen.
- *
- * Usually {@code true} on small screens, and {@code false} on large screens.
- */
- private boolean mIsNotificationPanelFullWidth;
+ private boolean mIsSmallScreen;
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -176,13 +176,17 @@
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
DumpManager dumpManager, QSLogger qsLogger,
FooterActionsController footerActionsController,
- FooterActionsViewModel.Factory footerActionsViewModelFactory) {
+ FooterActionsViewModel.Factory footerActionsViewModelFactory,
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ FeatureFlags featureFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
mQsComponentFactory = qsComponentFactory;
mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
mLogger = qsLogger;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
commandQueue.observe(getLifecycle(), this);
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
@@ -607,7 +611,7 @@
@Override
public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
- mIsNotificationPanelFullWidth = isFullWidth;
+ mIsSmallScreen = isFullWidth;
}
@Override
@@ -710,7 +714,7 @@
}
private float calculateAlphaProgress(float panelExpansionFraction) {
- if (mIsNotificationPanelFullWidth) {
+ if (mIsSmallScreen) {
// Small screens. QS alpha is not animated.
return 1;
}
@@ -745,7 +749,12 @@
// Alpha progress should be linear on lockscreen shade expansion.
return progress;
}
- return ShadeInterpolation.getContentAlpha(progress);
+ if (mIsSmallScreen || !mFeatureFlags.isEnabled(
+ Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(progress);
+ } else {
+ return mLargeScreenShadeInterpolator.getQsAlpha(progress);
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index c8c1337..7cfe232 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -57,7 +57,8 @@
import javax.inject.Inject;
-class ImageExporter {
+/** A class to help with exporting screenshot to storage. */
+public class ImageExporter {
private static final String TAG = LogConfig.logTag(ImageExporter.class);
static final Duration PENDING_ENTRY_TTL = Duration.ofHours(24);
@@ -90,7 +91,7 @@
private final FeatureFlags mFlags;
@Inject
- ImageExporter(ContentResolver resolver, FeatureFlags flags) {
+ public ImageExporter(ContentResolver resolver, FeatureFlags flags) {
mResolver = resolver;
mFlags = flags;
}
@@ -148,7 +149,7 @@
*
* @return a listenable future result
*/
- ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+ public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
UserHandle owner) {
return export(executor, requestId, bitmap, ZonedDateTime.now(), owner);
}
@@ -181,13 +182,14 @@
);
}
- static class Result {
- Uri uri;
- UUID requestId;
- String fileName;
- long timestamp;
- CompressFormat format;
- boolean published;
+ /** The result returned by the task exporting screenshots to storage. */
+ public static class Result {
+ public Uri uri;
+ public UUID requestId;
+ public String fileName;
+ public long timestamp;
+ public CompressFormat format;
+ public boolean published;
@Override
public String toString() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8721d71..557e95c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -419,6 +419,10 @@
return;
}
+ mScreenBitmap = screenshot.getBitmap();
+ String oldPackageName = mPackageName;
+ mPackageName = screenshot.getPackageNameString();
+
if (!isUserSetupComplete(Process.myUserHandle())) {
Log.w(TAG, "User setup not complete, displaying toast only");
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
@@ -433,10 +437,6 @@
mScreenshotTakenInPortrait =
mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
- String oldPackageName = mPackageName;
- mPackageName = screenshot.getPackageNameString();
-
- mScreenBitmap = screenshot.getBitmap();
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index fc94aed..7a62bae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -93,13 +93,7 @@
@UiEvent(doc = "User has discarded the result of a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
@UiEvent(doc = "A screenshot has been taken and saved to work profile")
- SCREENSHOT_SAVED_TO_WORK_PROFILE(1240),
- @UiEvent(doc = "Notes application triggered the screenshot for notes")
- SCREENSHOT_FOR_NOTE_TRIGGERED(1308),
- @UiEvent(doc = "User accepted the screenshot to be sent to the notes app")
- SCREENSHOT_FOR_NOTE_ACCEPTED(1309),
- @UiEvent(doc = "User cancelled the screenshot for notes app flow")
- SCREENSHOT_FOR_NOTE_CANCELLED(1310);
+ SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3be2417..c44f4f3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2883,7 +2883,10 @@
mHeadsUpStartHeight = startHeight;
float scrimMinFraction;
if (mSplitShadeEnabled) {
- boolean highHun = mHeadsUpStartHeight * 2.5 > mSplitShadeScrimTransitionDistance;
+ boolean highHun = mHeadsUpStartHeight * 2.5
+ >
+ (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
+ ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
// if HUN height is higher than 40% of predefined transition distance, it means HUN
// is too high for regular transition. In that case we need to calculate transition
// distance - here we take scrim transition distance as equal to shade transition
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
new file mode 100644
index 0000000..0519131
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+import android.util.MathUtils
+import com.android.systemui.animation.ShadeInterpolation
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when in portrait on a large screen. */
+internal class LargeScreenPortraitShadeInterpolator @Inject internal constructor() :
+ LargeScreenShadeInterpolator {
+
+ override fun getBehindScrimAlpha(fraction: Float): Float {
+ return MathUtils.constrainedMap(0f, 1f, 0f, 0.3f, fraction)
+ }
+
+ override fun getNotificationScrimAlpha(fraction: Float): Float {
+ return MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, fraction)
+ }
+
+ override fun getNotificationContentAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+
+ override fun getNotificationFooterAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+
+ override fun getQsAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
new file mode 100644
index 0000000..671dfc9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+/** An interpolator interface for the shade expansion. */
+interface LargeScreenShadeInterpolator {
+
+ /** Returns the alpha for the behind/back scrim. */
+ fun getBehindScrimAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notification scrim. */
+ fun getNotificationScrimAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notifications. */
+ fun getNotificationContentAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notifications footer (Manager, Clear All). */
+ fun getNotificationFooterAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the QS panel. */
+ fun getQsAlpha(fraction: Float): Float
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
new file mode 100644
index 0000000..fd57f21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.LargeScreenUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when on large screens. */
+@SysUISingleton
+internal class LargeScreenShadeInterpolatorImpl
+@Inject
+internal constructor(
+ configurationController: ConfigurationController,
+ private val context: Context,
+ private val splitShadeInterpolator: SplitShadeInterpolator,
+ private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
+) : LargeScreenShadeInterpolator {
+
+ private var inSplitShade = false
+
+ init {
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ }
+ )
+ updateResources()
+ }
+
+ private fun updateResources() {
+ inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
+ }
+
+ private val impl: LargeScreenShadeInterpolator
+ get() =
+ if (inSplitShade) {
+ splitShadeInterpolator
+ } else {
+ portraitShadeInterpolator
+ }
+
+ override fun getBehindScrimAlpha(fraction: Float) = impl.getBehindScrimAlpha(fraction)
+
+ override fun getNotificationScrimAlpha(fraction: Float) =
+ impl.getNotificationScrimAlpha(fraction)
+
+ override fun getNotificationContentAlpha(fraction: Float) =
+ impl.getNotificationContentAlpha(fraction)
+
+ override fun getNotificationFooterAlpha(fraction: Float) =
+ impl.getNotificationFooterAlpha(fraction)
+
+ override fun getQsAlpha(fraction: Float) = impl.getQsAlpha(fraction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 218e897..4e1c272 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -23,6 +23,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -45,7 +47,8 @@
private val scrimController: ScrimController,
@Main private val resources: Resources,
private val statusBarStateController: SysuiStatusBarStateController,
- private val headsUpManager: HeadsUpManager
+ private val headsUpManager: HeadsUpManager,
+ private val featureFlags: FeatureFlags,
) {
private var inSplitShade = false
@@ -106,7 +109,8 @@
// in case of HUN we can't always use predefined distances to manage scrim
// transition because dragDownPxAmount can start from value bigger than
// splitShadeScrimTransitionDistance
- !headsUpManager.isTrackingHeadsUp
+ !headsUpManager.isTrackingHeadsUp &&
+ !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
private fun isScreenUnlocked() =
statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
new file mode 100644
index 0000000..423ba8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+import android.util.MathUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the split shade. */
+internal class SplitShadeInterpolator @Inject internal constructor() :
+ LargeScreenShadeInterpolator {
+
+ override fun getBehindScrimAlpha(fraction: Float): Float {
+ // Start delay: 0%
+ // Duration: 40%
+ // End: 40%
+ return mapFraction(start = 0f, end = 0.4f, fraction)
+ }
+
+ override fun getNotificationScrimAlpha(fraction: Float): Float {
+ // Start delay: 39%
+ // Duration: 27%
+ // End: 66%
+ return mapFraction(start = 0.39f, end = 0.66f, fraction)
+ }
+
+ override fun getNotificationContentAlpha(fraction: Float): Float {
+ return getNotificationScrimAlpha(fraction)
+ }
+
+ override fun getNotificationFooterAlpha(fraction: Float): Float {
+ // Start delay: 57.6%
+ // Duration: 32.1%
+ // End: 89.7%
+ return mapFraction(start = 0.576f, end = 0.897f, fraction)
+ }
+
+ override fun getQsAlpha(fraction: Float): Float {
+ return getNotificationScrimAlpha(fraction)
+ }
+
+ private fun mapFraction(start: Float, end: Float, fraction: Float) =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 0f,
+ /* rangeMax= */ 1f,
+ /* valueMin= */ start,
+ /* valueMax= */ end,
+ /* value= */ fraction
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8f1e0a1..3709a13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,7 +39,10 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
@@ -216,7 +219,15 @@
if (ambientState.isBouncerInTransit()) {
viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen() || !flags.isEnabled(
+ Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+ } else {
+ LargeScreenShadeInterpolator interpolator =
+ ambientState.getLargeScreenShadeInterpolator();
+ viewState.setAlpha(interpolator.getNotificationContentAlpha(expansion));
+ }
}
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b0ad6a1..37538a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -500,8 +500,10 @@
private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach {
- val textColor = regionSamplers.getValue(it).currentForegroundColor()
- it.setPrimaryTextColor(textColor)
+ val textColor = regionSamplers.get(it)?.currentForegroundColor()
+ if (textColor != null) {
+ it.setPrimaryTextColor(textColor)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index bbabde3..1818dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -153,7 +153,6 @@
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
private boolean mUpdateSelfBackgroundOnUpdate = true;
- private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
private boolean mIsFaded;
private boolean mAnimatePinnedRoundness = false;
@@ -209,11 +208,6 @@
*/
private boolean mUserExpanded;
/**
- * Whether the blocking helper is showing on this notification (even if dismissed)
- */
- private boolean mIsBlockingHelperShowing;
-
- /**
* Has this notification been expanded while it was pinned
*/
private boolean mExpandedWhenPinned;
@@ -1565,18 +1559,6 @@
}
}
- public void setBlockingHelperShowing(boolean isBlockingHelperShowing) {
- mIsBlockingHelperShowing = isBlockingHelperShowing;
- }
-
- public boolean isBlockingHelperShowing() {
- return mIsBlockingHelperShowing;
- }
-
- public boolean isBlockingHelperShowingAndTranslationFinished() {
- return mIsBlockingHelperShowing && mNotificationTranslationFinished;
- }
-
@Override
public View getShelfTransformationTarget() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -2155,10 +2137,7 @@
@Override
public void setTranslation(float translationX) {
invalidate();
- if (isBlockingHelperShowingAndTranslationFinished()) {
- mGuts.setTranslationX(translationX);
- return;
- } else if (mDismissUsingRowTranslationX) {
+ if (mDismissUsingRowTranslationX) {
setTranslationX(translationX);
} else if (mTranslateableViews != null) {
// Translate the group of views
@@ -2186,10 +2165,6 @@
return getTranslationX();
}
- if (isBlockingHelperShowingAndCanTranslate()) {
- return mGuts.getTranslationX();
- }
-
if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
// All of the views in the list should have same translation, just use first one.
return mTranslateableViews.get(0).getTranslationX();
@@ -2198,10 +2173,6 @@
return 0;
}
- private boolean isBlockingHelperShowingAndCanTranslate() {
- return areGutsExposed() && mIsBlockingHelperShowing && mNotificationTranslationFinished;
- }
-
public Animator getTranslateViewAnimator(final float leftTarget,
AnimatorUpdateListener listener) {
if (mTranslateAnim != null) {
@@ -2223,9 +2194,6 @@
@Override
public void onAnimationEnd(Animator anim) {
- if (mIsBlockingHelperShowing) {
- mNotificationTranslationFinished = true;
- }
if (!cancelled && leftTarget == 0) {
if (mMenuRow != null) {
mMenuRow.resetMenu();
@@ -2805,9 +2773,10 @@
int intrinsicBefore = getIntrinsicHeight();
mSensitive = sensitive;
mSensitiveHiddenInGeneral = hideSensitive;
- if (intrinsicBefore != getIntrinsicHeight()) {
- // The animation has a few flaws and is highly visible, so jump cut instead.
- notifyHeightChanged(false /* needsAnimation */);
+ int intrinsicAfter = getIntrinsicHeight();
+ if (intrinsicBefore != intrinsicAfter) {
+ boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
+ notifyHeightChanged(needsAnimation);
}
}
@@ -2864,13 +2833,19 @@
View[] publicViews = new View[]{mPublicLayout};
View[] hiddenChildren = showingPublic ? privateViews : publicViews;
View[] shownChildren = showingPublic ? publicViews : privateViews;
+ // disappear/appear overlap: 10 percent of duration
+ long overlap = duration / 10;
+ // disappear duration: 1/3 of duration + half of overlap
+ long disappearDuration = duration / 3 + overlap / 2;
+ // appear duration: 2/3 of duration + half of overlap
+ long appearDuration = (duration - disappearDuration) + overlap / 2;
for (final View hiddenView : hiddenChildren) {
hiddenView.setVisibility(View.VISIBLE);
hiddenView.animate().cancel();
hiddenView.animate()
.alpha(0f)
.setStartDelay(delay)
- .setDuration(duration)
+ .setDuration(disappearDuration)
.withEndAction(() -> {
hiddenView.setVisibility(View.INVISIBLE);
resetAllContentAlphas();
@@ -2882,8 +2857,8 @@
showView.animate().cancel();
showView.animate()
.alpha(1f)
- .setStartDelay(delay)
- .setDuration(duration);
+ .setStartDelay(delay + duration - appearDuration)
+ .setDuration(appearDuration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 93f0812..596bdc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -238,12 +238,11 @@
}
public void openControls(
- boolean shouldDoCircularReveal,
int x,
int y,
boolean needsFalsingProtection,
@Nullable Runnable onAnimationEnd) {
- animateOpen(shouldDoCircularReveal, x, y, onAnimationEnd);
+ animateOpen(x, y, onAnimationEnd);
setExposed(true /* exposed */, needsFalsingProtection);
}
@@ -300,7 +299,7 @@
if (mGutsContent == null
|| !mGutsContent.handleCloseControls(save, force)) {
// We only want to do a circular reveal if we're not showing the blocking helper.
- animateClose(x, y, true /* shouldDoCircularReveal */);
+ animateClose(x, y);
setExposed(false, mNeedsFalsingProtection);
if (mClosedListener != null) {
@@ -309,66 +308,45 @@
}
}
- /** Animates in the guts view via either a fade or a circular reveal. */
- private void animateOpen(
- boolean shouldDoCircularReveal, int x, int y, @Nullable Runnable onAnimationEnd) {
+ /** Animates in the guts view with a circular reveal. */
+ private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
if (isAttachedToWindow()) {
- if (shouldDoCircularReveal) {
- double horz = Math.max(getWidth() - x, x);
- double vert = Math.max(getHeight() - y, y);
- float r = (float) Math.hypot(horz, vert);
- // Make sure we'll be visible after the circular reveal
- setAlpha(1f);
- // Circular reveal originating at (x, y)
- Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimateOpenListener(onAnimationEnd));
- a.start();
- } else {
- // Fade in content
- this.setAlpha(0f);
- this.animate()
- .alpha(1f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setListener(new AnimateOpenListener(onAnimationEnd))
- .start();
- }
+ double horz = Math.max(getWidth() - x, x);
+ double vert = Math.max(getHeight() - y, y);
+ float r = (float) Math.hypot(horz, vert);
+ // Make sure we'll be visible after the circular reveal
+ setAlpha(1f);
+ // Circular reveal originating at (x, y)
+ Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimateOpenListener(onAnimationEnd));
+ a.start();
+
} else {
Log.w(TAG, "Failed to animate guts open");
}
}
- /** Animates out the guts view via either a fade or a circular reveal. */
+ /** Animates out the guts view with a circular reveal. */
@VisibleForTesting
- void animateClose(int x, int y, boolean shouldDoCircularReveal) {
+ void animateClose(int x, int y) {
if (isAttachedToWindow()) {
- if (shouldDoCircularReveal) {
- // Circular reveal originating at (x, y)
- if (x == -1 || y == -1) {
- x = (getLeft() + getRight()) / 2;
- y = (getTop() + getHeight() / 2);
- }
- double horz = Math.max(getWidth() - x, x);
- double vert = Math.max(getHeight() - y, y);
- float r = (float) Math.hypot(horz, vert);
- Animator a = ViewAnimationUtils.createCircularReveal(this,
- x, y, r, 0);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
- a.start();
- } else {
- // Fade in the blocking helper.
- this.animate()
- .alpha(0f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .setListener(new AnimateCloseListener(this, /* view */mGutsContent))
- .start();
+ // Circular reveal originating at (x, y)
+ if (x == -1 || y == -1) {
+ x = (getLeft() + getRight()) / 2;
+ y = (getTop() + getHeight() / 2);
}
+ double horz = Math.max(getWidth() - x, x);
+ double vert = Math.max(getHeight() - y, y);
+ float r = (float) Math.hypot(horz, vert);
+ Animator a = ViewAnimationUtils.createCircularReveal(this,
+ x, y, r, 0);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
+ a.start();
} else {
Log.w(TAG, "Failed to animate guts close");
mGutsContent.onFinishedClosing();
@@ -449,7 +427,7 @@
return mGutsContent != null && mGutsContent.isLeavebehind();
}
- /** Listener for animations executed in {@link #animateOpen(boolean, int, int, Runnable)}. */
+ /** Listener for animations executed in {@link #animateOpen(int, int, Runnable)}. */
private static class AnimateOpenListener extends AnimatorListenerAdapter {
final Runnable mOnAnimationEnd;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 06d4080..46f1bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -629,7 +629,6 @@
!mAccessibilityManager.isTouchExplorationEnabled());
guts.openControls(
- !row.isBlockingHelperShowing(),
x,
y,
needsFalsingProtection,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6f4d6d9..77ede04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -29,6 +29,8 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -55,6 +57,8 @@
private final SectionProvider mSectionProvider;
private final BypassController mBypassController;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
/**
* Used to read bouncer states.
*/
@@ -84,7 +88,7 @@
private float mExpandingVelocity;
private boolean mPanelTracking;
private boolean mExpansionChanging;
- private boolean mPanelFullWidth;
+ private boolean mIsSmallScreen;
private boolean mPulsing;
private boolean mUnlockHintRunning;
private float mHideAmount;
@@ -252,10 +256,14 @@
@NonNull DumpManager dumpManager,
@NonNull SectionProvider sectionProvider,
@NonNull BypassController bypassController,
- @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ @NonNull FeatureFlags featureFlags) {
mSectionProvider = sectionProvider;
mBypassController = bypassController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
reload(context);
dumpManager.registerDumpable(this);
}
@@ -574,12 +582,12 @@
return mPanelTracking;
}
- public boolean isPanelFullWidth() {
- return mPanelFullWidth;
+ public boolean isSmallScreen() {
+ return mIsSmallScreen;
}
- public void setPanelFullWidth(boolean panelFullWidth) {
- mPanelFullWidth = panelFullWidth;
+ public void setSmallScreen(boolean smallScreen) {
+ mIsSmallScreen = smallScreen;
}
public void setUnlockHintRunning(boolean unlockHintRunning) {
@@ -736,6 +744,14 @@
return mIsClosing;
}
+ public LargeScreenShadeInterpolator getLargeScreenShadeInterpolator() {
+ return mLargeScreenShadeInterpolator;
+ }
+
+ public FeatureFlags getFeatureFlags() {
+ return mFeatureFlags;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("mTopPadding=" + mTopPadding);
@@ -751,7 +767,7 @@
pw.println("mDimmed=" + mDimmed);
pw.println("mStatusBarState=" + mStatusBarState);
pw.println("mExpansionChanging=" + mExpansionChanging);
- pw.println("mPanelFullWidth=" + mPanelFullWidth);
+ pw.println("mPanelFullWidth=" + mIsSmallScreen);
pw.println("mPulsing=" + mPulsing);
pw.println("mPulseHeight=" + mPulseHeight);
pw.println("mTrackedHeadsUpRow.key=" + logKey(mTrackedHeadsUpRow));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 977e1bb..47d8f48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5223,7 +5223,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setIsFullWidth(boolean isFullWidth) {
- mAmbientState.setPanelFullWidth(isFullWidth);
+ mAmbientState.setSmallScreen(isFullWidth);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 14b0763..02621fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -70,7 +70,6 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -171,7 +170,6 @@
private final CentralSurfaces mCentralSurfaces;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- private final ShadeTransitionController mShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
@@ -662,7 +660,6 @@
NotifPipelineFlags notifPipelineFlags,
NotifCollection notifCollection,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
- ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
@@ -694,7 +691,6 @@
mMetricsLogger = metricsLogger;
mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
- mShadeTransitionController = shadeTransitionController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mResources = resources;
@@ -777,7 +773,6 @@
mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
mLockscreenShadeTransitionController.setStackScroller(this);
- mShadeTransitionController.setNotificationStackScrollLayoutController(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index a425792..5516ede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,6 +29,9 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.LegacySourceType;
@@ -135,7 +138,6 @@
AmbientState ambientState) {
for (ExpandableView view : algorithmState.visibleChildren) {
final ViewState viewState = view.getViewState();
-
final boolean isHunGoingToShade = ambientState.isShadeExpanded()
&& view == ambientState.getTrackedHeadsUpRow();
@@ -148,9 +150,14 @@
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
float expansion = ambientState.getExpansionFraction();
- viewState.setAlpha(ambientState.isBouncerInTransit()
- ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
- : ShadeInterpolation.getContentAlpha(expansion));
+ if (ambientState.isBouncerInTransit()) {
+ viewState.setAlpha(
+ BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion));
+ } else if (view instanceof FooterView) {
+ viewState.setAlpha(interpolateFooterAlpha(ambientState));
+ } else {
+ viewState.setAlpha(interpolateNotificationContentAlpha(ambientState));
+ }
}
// For EmptyShadeView if on keyguard, we need to control the alpha to create
@@ -182,6 +189,28 @@
}
}
+ private float interpolateFooterAlpha(AmbientState ambientState) {
+ float expansion = ambientState.getExpansionFraction();
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen()
+ || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(expansion);
+ }
+ LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+ return interpolator.getNotificationFooterAlpha(expansion);
+ }
+
+ private float interpolateNotificationContentAlpha(AmbientState ambientState) {
+ float expansion = ambientState.getExpansionFraction();
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen()
+ || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(expansion);
+ }
+ LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+ return interpolator.getNotificationContentAlpha(expansion);
+ }
+
/**
* How expanded or collapsed notifications are when pulling down the shade.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8fd9675..f5d2eee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -54,15 +54,18 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
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.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -206,7 +209,6 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final SysuiStatusBarStateController mStatusBarStateController;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -245,6 +247,8 @@
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -265,12 +269,16 @@
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
- private final Consumer<Float> mScrimAlphaConsumer =
- (Float alpha) -> {
+ private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
+ (ScrimAlpha alphas) -> {
+ mInFrontAlpha = alphas.getFrontAlpha();
mScrimInFront.setViewAlpha(mInFrontAlpha);
+
+ mNotificationsAlpha = alphas.getNotificationsAlpha();
mNotificationsScrim.setViewAlpha(mNotificationsAlpha);
- mBehindAlpha = alpha;
- mScrimBehind.setViewAlpha(alpha);
+
+ mBehindAlpha = alphas.getBehindAlpha();
+ mScrimBehind.setViewAlpha(mBehindAlpha);
};
Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
@@ -292,15 +300,17 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- SysuiStatusBarStateController sysuiStatusBarStateController,
- @Main CoroutineDispatcher mainDispatcher) {
+ @Main CoroutineDispatcher mainDispatcher,
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ FeatureFlags featureFlags) {
mScrimStateListener = lightBarController::setScrimState;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mStatusBarStateController = sysuiStatusBarStateController;
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
@@ -400,7 +410,7 @@
collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
mPrimaryBouncerToGoneTransition, mMainDispatcher);
- collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(),
+ collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
}
@@ -837,16 +847,21 @@
if (!mScreenOffAnimationController.shouldExpandNotifications()
&& !mAnimatingPanelExpansionOnUnlock
&& !occluding) {
- if (mClipsQsScrim) {
+ if (mTransparentScrimBackground) {
+ mBehindAlpha = 0;
+ mNotificationsAlpha = 0;
+ } else if (mClipsQsScrim) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
- mNotificationsAlpha =
- mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha = 1;
+ mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
} else {
- if (mTransparentScrimBackground) {
- mBehindAlpha = 0;
- mNotificationsAlpha = 0;
+ if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+ mPanelExpansionFraction * mDefaultScrimAlpha);
+ mNotificationsAlpha =
+ mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+ mPanelExpansionFraction);
} else {
// Behind scrim will finish fading in at 30% expansion.
float behindFraction = MathUtils
@@ -1100,8 +1115,7 @@
mBehindAlpha = 1;
}
// Prevent notification scrim flicker when transitioning away from keyguard.
- if (mKeyguardStateController.isKeyguardGoingAway()
- && !mStatusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (mKeyguardStateController.isKeyguardGoingAway()) {
mNotificationsAlpha = 0;
}
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 3c32131..b6a3ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -733,7 +733,9 @@
} else {
showBouncerOrKeyguard(hideBouncerWhenShowing);
}
- hideAlternateBouncer(false);
+ if (hideBouncerWhenShowing) {
+ hideAlternateBouncer(false);
+ }
mKeyguardUpdateManager.sendKeyguardReset();
updateStates();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 078a00d..189f2e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -27,6 +27,7 @@
import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -45,6 +46,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
@@ -74,6 +76,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wmshell.BubblesManager;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -81,6 +84,7 @@
import dagger.Lazy;
+
/**
* Status bar implementation of {@link NotificationActivityStarter}.
*/
@@ -572,16 +576,29 @@
});
// not immersive & a fullscreen alert should be shown
- final PendingIntent fullscreenIntent =
+ final PendingIntent fullScreenIntent =
entry.getSbn().getNotification().fullScreenIntent;
- mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
+ mLogger.logSendingFullScreenIntent(entry, fullScreenIntent);
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
mCentralSurfaces.wakeUpForFullScreenIntent();
- fullscreenIntent.send();
+ fullScreenIntent.send();
entry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
+
+ String activityName;
+ List<ResolveInfo> resolveInfos = fullScreenIntent.queryIntentComponents(0);
+ if (resolveInfos.size() > 0 && resolveInfos.get(0) != null
+ && resolveInfos.get(0).activityInfo != null
+ && resolveInfos.get(0).activityInfo.name != null) {
+ activityName = resolveInfos.get(0).activityInfo.name;
+ } else {
+ activityName = "";
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.FULL_SCREEN_INTENT_LAUNCHED,
+ fullScreenIntent.getCreatorUid(),
+ activityName);
} catch (PendingIntent.CanceledException e) {
// ignore
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 159f689..4156fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -51,8 +51,8 @@
)
}
- fun logOnLost(network: Network) {
- LoggerHelper.logOnLost(buffer, TAG, network)
+ fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+ LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 85729c1..19f0242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -24,9 +24,11 @@
import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState {
+enum class DataConnectionState : Diffable<DataConnectionState> {
Connected,
Connecting,
Disconnected,
@@ -34,7 +36,17 @@
Suspended,
HandoverInProgress,
Unknown,
- Invalid,
+ Invalid;
+
+ override fun logDiffs(prevVal: DataConnectionState, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_CONNECTION_STATE, name)
+ }
+ }
+
+ companion object {
+ private const val COL_CONNECTION_STATE = "connectionState"
+ }
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
deleted file mode 100644
index ed7f60b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.model
-
-import android.annotation.IntRange
-import android.telephony.CellSignalStrength
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-
-/**
- * Data class containing all of the relevant information for a particular line of service, known as
- * a Subscription in the telephony world. These models are the result of a single telephony listener
- * which has many callbacks which each modify some particular field on this object.
- *
- * The design goal here is to de-normalize fields from the system into our model fields below. So
- * any new field that needs to be tracked should be copied into this data class rather than
- * threading complex system objects through the pipeline.
- */
-data class MobileConnectionModel(
- /** Fields below are from [ServiceStateListener.onServiceStateChanged] */
- val isEmergencyOnly: Boolean = false,
- val isRoaming: Boolean = false,
- /**
- * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
- * current registered operator name in short alphanumeric format. In some cases this name might
- * be preferred over other methods of calculating the network name
- */
- val operatorAlphaShort: String? = null,
-
- /**
- * TODO (b/263167683): Clarify this field
- *
- * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
- * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
- * connection to be in-service if either the voice registration state is IN_SERVICE or the data
- * registration state is IN_SERVICE and NOT IWLAN.
- */
- val isInService: Boolean = false,
-
- /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
- val isGsm: Boolean = false,
- @IntRange(from = 0, to = 4)
- val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
- @IntRange(from = 0, to = 4)
- val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-
- /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */
- val dataConnectionState: DataConnectionState = Disconnected,
-
- /**
- * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the
- * values
- */
- val dataActivityDirection: DataActivityModel =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
-
- /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */
- val carrierNetworkChangeActive: Boolean = false,
-
- /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */
-
- /**
- * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
- * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
- */
- val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
-) : Diffable<MobileConnectionModel> {
- override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
- if (prevVal.dataConnectionState != dataConnectionState) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
- }
-
- if (prevVal.isEmergencyOnly != isEmergencyOnly) {
- row.logChange(COL_EMERGENCY, isEmergencyOnly)
- }
-
- if (prevVal.isRoaming != isRoaming) {
- row.logChange(COL_ROAMING, isRoaming)
- }
-
- if (prevVal.operatorAlphaShort != operatorAlphaShort) {
- row.logChange(COL_OPERATOR, operatorAlphaShort)
- }
-
- if (prevVal.isInService != isInService) {
- row.logChange(COL_IS_IN_SERVICE, isInService)
- }
-
- if (prevVal.isGsm != isGsm) {
- row.logChange(COL_IS_GSM, isGsm)
- }
-
- if (prevVal.cdmaLevel != cdmaLevel) {
- row.logChange(COL_CDMA_LEVEL, cdmaLevel)
- }
-
- if (prevVal.primaryLevel != primaryLevel) {
- row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- }
-
- if (prevVal.dataActivityDirection.hasActivityIn != dataActivityDirection.hasActivityIn) {
- row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
- }
-
- if (prevVal.dataActivityDirection.hasActivityOut != dataActivityDirection.hasActivityOut) {
- row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
- }
-
- if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
- }
-
- if (prevVal.resolvedNetworkType != resolvedNetworkType) {
- row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
- }
- }
-
- override fun logFull(row: TableRowLogger) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
- row.logChange(COL_EMERGENCY, isEmergencyOnly)
- row.logChange(COL_ROAMING, isRoaming)
- row.logChange(COL_OPERATOR, operatorAlphaShort)
- row.logChange(COL_IS_IN_SERVICE, isInService)
- row.logChange(COL_IS_GSM, isGsm)
- row.logChange(COL_CDMA_LEVEL, cdmaLevel)
- row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
- row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
- row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
- }
-
- @VisibleForTesting
- companion object {
- const val COL_EMERGENCY = "EmergencyOnly"
- const val COL_ROAMING = "Roaming"
- const val COL_OPERATOR = "OperatorName"
- const val COL_IS_IN_SERVICE = "IsInService"
- const val COL_IS_GSM = "IsGsm"
- const val COL_CDMA_LEVEL = "CdmaLevel"
- const val COL_PRIMARY_LEVEL = "PrimaryLevel"
- const val COL_CONNECTION_STATE = "ConnectionState"
- const val COL_ACTIVITY_DIRECTION_IN = "DataActivity.In"
- const val COL_ACTIVITY_DIRECTION_OUT = "DataActivity.Out"
- const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
- const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index 5562e73..cf7a313 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,8 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -26,11 +30,19 @@
* on whether or not the display info contains an override type, we may have to call different
* methods on [MobileMappingsProxy] to generate an icon lookup key.
*/
-sealed interface ResolvedNetworkType {
+sealed interface ResolvedNetworkType : Diffable<ResolvedNetworkType> {
val lookupKey: String
+ override fun logDiffs(prevVal: ResolvedNetworkType, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_NETWORK_TYPE, this.toString())
+ }
+ }
+
object UnknownNetworkType : ResolvedNetworkType {
- override val lookupKey: String = "unknown"
+ override val lookupKey: String = MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN)
+
+ override fun toString(): String = "Unknown"
}
data class DefaultNetworkType(
@@ -47,5 +59,11 @@
override val lookupKey: String = "cwf"
val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
+
+ override fun toString(): String = "CarrierMerged"
+ }
+
+ companion object {
+ private const val COL_NETWORK_TYPE = "networkType"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 6187f64..90c32dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.StateFlow
/**
@@ -45,11 +46,57 @@
*/
val tableLogBuffer: TableLogBuffer
+ /** True if the [android.telephony.ServiceState] says this connection is emergency calls only */
+ val isEmergencyOnly: StateFlow<Boolean>
+
+ /** True if [android.telephony.ServiceState] says we are roaming */
+ val isRoaming: StateFlow<Boolean>
+
/**
- * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
- * listener + model.
+ * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+ * current registered operator name in short alphanumeric format. In some cases this name might
+ * be preferred over other methods of calculating the network name
*/
- val connectionInfo: StateFlow<MobileConnectionModel>
+ val operatorAlphaShort: StateFlow<String?>
+
+ /**
+ * TODO (b/263167683): Clarify this field
+ *
+ * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
+ * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
+ * connection to be in-service if either the voice registration state is IN_SERVICE or the data
+ * registration state is IN_SERVICE and NOT IWLAN.
+ */
+ val isInService: StateFlow<Boolean>
+
+ /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
+ val isGsm: StateFlow<Boolean>
+
+ /**
+ * There is still specific logic in the pipeline that calls out CDMA level explicitly. This
+ * field is not completely orthogonal to [primaryLevel], because CDMA could be primary.
+ */
+ // @IntRange(from = 0, to = 4)
+ val cdmaLevel: StateFlow<Int>
+
+ /** [android.telephony.SignalStrength]'s concept of the overall signal level */
+ // @IntRange(from = 0, to = 4)
+ val primaryLevel: StateFlow<Int>
+
+ /** The current data connection state. See [DataConnectionState] */
+ val dataConnectionState: StateFlow<DataConnectionState>
+
+ /** The current data activity direction. See [DataActivityModel] */
+ val dataActivityDirection: StateFlow<DataActivityModel>
+
+ /** True if there is currently a carrier network change in process */
+ val carrierNetworkChangeActive: StateFlow<Boolean>
+
+ /**
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+ */
+ val resolvedNetworkType: StateFlow<ResolvedNetworkType>
/** The total number of levels. Used with [SignalDrawable]. */
val numberOfLevels: StateFlow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
new file mode 100644
index 0000000..809772e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Demo version of [MobileConnectionRepository]. Note that this class shares all of its flows using
+ * [SharingStarted.WhileSubscribed()] to give the same semantics as using a regular
+ * [MutableStateFlow] while still logging all of the inputs in the same manor as the production
+ * repos.
+ */
+class DemoMobileConnectionRepository(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ val scope: CoroutineScope,
+) : MobileConnectionRepository {
+ private val _isEmergencyOnly = MutableStateFlow(false)
+ override val isEmergencyOnly =
+ _isEmergencyOnly
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_EMERGENCY,
+ _isEmergencyOnly.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isEmergencyOnly.value)
+
+ private val _isRoaming = MutableStateFlow(false)
+ override val isRoaming =
+ _isRoaming
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_ROAMING,
+ _isRoaming.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isRoaming.value)
+
+ private val _operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val operatorAlphaShort =
+ _operatorAlphaShort
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_OPERATOR,
+ _operatorAlphaShort.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _operatorAlphaShort.value)
+
+ private val _isInService = MutableStateFlow(false)
+ override val isInService =
+ _isInService
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_IN_SERVICE,
+ _isInService.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
+
+ private val _isGsm = MutableStateFlow(false)
+ override val isGsm =
+ _isGsm
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_GSM,
+ _isGsm.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isGsm.value)
+
+ private val _cdmaLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val cdmaLevel =
+ _cdmaLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CDMA_LEVEL,
+ _cdmaLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _cdmaLevel.value)
+
+ private val _primaryLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val primaryLevel =
+ _primaryLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_PRIMARY_LEVEL,
+ _primaryLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _primaryLevel.value)
+
+ private val _dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+ override val dataConnectionState =
+ _dataConnectionState
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataConnectionState.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _dataConnectionState.value)
+
+ private val _dataActivityDirection =
+ MutableStateFlow(
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ )
+ )
+ override val dataActivityDirection =
+ _dataActivityDirection
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataActivityDirection.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _dataActivityDirection.value)
+
+ private val _carrierNetworkChangeActive = MutableStateFlow(false)
+ override val carrierNetworkChangeActive =
+ _carrierNetworkChangeActive
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_NETWORK_CHANGE,
+ _carrierNetworkChangeActive.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierNetworkChangeActive.value)
+
+ private val _resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+ MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
+ override val resolvedNetworkType =
+ _resolvedNetworkType
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _resolvedNetworkType.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _resolvedNetworkType.value)
+
+ override val numberOfLevels = MutableStateFlow(MobileConnectionRepository.DEFAULT_NUM_LEVELS)
+
+ override val dataEnabled = MutableStateFlow(true)
+
+ override val cdmaRoaming = MutableStateFlow(false)
+
+ override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
+
+ /**
+ * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
+ * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
+ * repository.
+ */
+ fun processDemoMobileEvent(
+ event: FakeNetworkEventModel.Mobile,
+ resolvedNetworkType: ResolvedNetworkType,
+ ) {
+ // This is always true here, because we split out disabled states at the data-source level
+ dataEnabled.value = true
+ networkName.value = NetworkNameModel.IntentDerived(event.name)
+
+ cdmaRoaming.value = event.roaming
+ _isRoaming.value = event.roaming
+ // TODO(b/261029387): not yet supported
+ _isEmergencyOnly.value = false
+ _operatorAlphaShort.value = event.name
+ _isInService.value = (event.level ?: 0) > 0
+ // TODO(b/261029387): not yet supported
+ _isGsm.value = false
+ _cdmaLevel.value = event.level ?: 0
+ _primaryLevel.value = event.level ?: 0
+ // TODO(b/261029387): not yet supported
+ _dataConnectionState.value = DataConnectionState.Connected
+ _dataActivityDirection.value =
+ (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ _carrierNetworkChangeActive.value = event.carrierNetworkChange
+ _resolvedNetworkType.value = resolvedNetworkType
+ }
+
+ fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
+ // This is always true here, because we split out disabled states at the data-source level
+ dataEnabled.value = true
+ networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
+ numberOfLevels.value = event.numberOfLevels
+ cdmaRoaming.value = false
+ _primaryLevel.value = event.level
+ _cdmaLevel.value = event.level
+ _dataActivityDirection.value = event.activity.toMobileDataActivityModel()
+
+ // These fields are always the same for carrier-merged networks
+ _resolvedNetworkType.value = ResolvedNetworkType.CarrierMergedNetworkType
+ _dataConnectionState.value = DataConnectionState.Connected
+ _isRoaming.value = false
+ _isEmergencyOnly.value = false
+ _operatorAlphaShort.value = null
+ _isInService.value = true
+ _isGsm.value = false
+ _carrierNetworkChangeActive.value = false
+ }
+
+ companion object {
+ private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index e924832..3cafb73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -18,30 +18,22 @@
import android.content.Context
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import android.util.Log
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
-import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import javax.inject.Inject
@@ -183,7 +175,7 @@
private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
val tableLogBuffer =
logFactory.getOrCreate(
- "DemoMobileConnectionLog [$subId]",
+ "DemoMobileConnectionLog[$subId]",
MOBILE_CONNECTION_BUFFER_SIZE,
)
@@ -191,6 +183,7 @@
DemoMobileConnectionRepository(
subId,
tableLogBuffer,
+ scope,
)
return CacheContainer(repo, lastMobileState = null)
}
@@ -237,23 +230,18 @@
}
}
- private fun processEnabledMobileState(state: Mobile) {
+ private fun processEnabledMobileState(event: Mobile) {
// get or create the connection repo, and set its values
- val subId = state.subId ?: DEFAULT_SUB_ID
+ val subId = event.subId ?: DEFAULT_SUB_ID
maybeCreateSubscription(subId)
val connection = getRepoForSubId(subId)
- connectionRepoCache[subId]?.lastMobileState = state
+ connectionRepoCache[subId]?.lastMobileState = event
// TODO(b/261029387): until we have a command, use the most recent subId
defaultDataSubId.value = subId
- // This is always true here, because we split out disabled states at the data-source level
- connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.IntentDerived(state.name)
-
- connection.cdmaRoaming.value = state.roaming
- connection.connectionInfo.value = state.toMobileConnectionModel()
+ connection.processDemoMobileEvent(event, event.dataType.toResolvedNetworkType())
}
private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
@@ -272,13 +260,7 @@
defaultDataSubId.value = subId
val connection = getRepoForSubId(subId)
- // This is always true here, because we split out disabled states at the data-source level
- connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
- connection.numberOfLevels.value = event.numberOfLevels
- connection.cdmaRoaming.value = false
- connection.connectionInfo.value = event.toMobileConnectionModel()
- Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
+ connection.processCarrierMergedEvent(event)
}
private fun maybeRemoveSubscription(subId: Int?) {
@@ -332,29 +314,6 @@
private fun subIdsString(): String =
_subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
- private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
- return MobileConnectionModel(
- isEmergencyOnly = false, // TODO(b/261029387): not yet supported
- isRoaming = roaming,
- isInService = (level ?: 0) > 0,
- isGsm = false, // TODO(b/261029387): not yet supported
- cdmaLevel = level ?: 0,
- primaryLevel = level ?: 0,
- dataConnectionState =
- DataConnectionState.Connected, // TODO(b/261029387): not yet supported
- dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(),
- carrierNetworkChangeActive = carrierNetworkChange,
- resolvedNetworkType = dataType.toResolvedNetworkType()
- )
- }
-
- private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
- return createCarrierMergedConnectionModel(
- this.level,
- activity.toMobileDataActivityModel(),
- )
- }
-
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
return DefaultNetworkType(key)
@@ -364,8 +323,6 @@
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
-
- private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
}
}
@@ -374,18 +331,3 @@
/** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
var lastMobileState: Mobile?,
)
-
-class DemoMobileConnectionRepository(
- override val subId: Int,
- override val tableLogBuffer: TableLogBuffer,
-) : MobileConnectionRepository {
- override val connectionInfo = MutableStateFlow(MobileConnectionModel())
-
- override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
-
- override val dataEnabled = MutableStateFlow(true)
-
- override val cdmaRoaming = MutableStateFlow(false)
-
- override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 8f6a87b..94d6d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -16,18 +16,17 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.TelephonyManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
@@ -94,16 +93,6 @@
}
}
- override val connectionInfo: StateFlow<MobileConnectionModel> =
- combine(network, wifiRepository.wifiActivity) { network, activity ->
- if (network == null) {
- MobileConnectionModel()
- } else {
- createCarrierMergedConnectionModel(network.level, activity)
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
-
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow()
override val networkName: StateFlow<NetworkNameModel> =
@@ -129,34 +118,54 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
+ override val primaryLevel =
+ network
+ .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val cdmaLevel =
+ network
+ .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val dataActivityDirection = wifiRepository.wifiActivity
+
+ override val resolvedNetworkType =
+ network
+ .map {
+ if (it != null) {
+ ResolvedNetworkType.CarrierMergedNetworkType
+ } else {
+ ResolvedNetworkType.UnknownNetworkType
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ ResolvedNetworkType.UnknownNetworkType
+ )
+
+ override val dataConnectionState =
+ network
+ .map {
+ if (it != null) {
+ DataConnectionState.Connected
+ } else {
+ DataConnectionState.Disconnected
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected)
+
+ override val isRoaming = MutableStateFlow(false).asStateFlow()
+ override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
+ override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
+ override val isInService = MutableStateFlow(true).asStateFlow()
+ override val isGsm = MutableStateFlow(false).asStateFlow()
+ override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
+
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
companion object {
- /**
- * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
- * with the given [level] and [activity].
- */
- fun createCarrierMergedConnectionModel(
- level: Int,
- activity: DataActivityModel,
- ): MobileConnectionModel {
- return MobileConnectionModel(
- primaryLevel = level,
- cdmaLevel = level,
- dataActivityDirection = activity,
- // Here and below: These values are always the same for every carrier-merged
- // connection.
- resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- dataConnectionState = DataConnectionState.Connected,
- isRoaming = ROAMING,
- isEmergencyOnly = false,
- operatorAlphaShort = null,
- isInService = true,
- isGsm = false,
- carrierNetworkChangeActive = false,
- )
- }
-
// Carrier merged is never roaming
private const val ROAMING = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index a39ea0a..b3737ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -114,15 +114,147 @@
.flatMapLatest { it.cdmaRoaming }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
- override val connectionInfo =
+ override val isEmergencyOnly =
activeRepo
- .flatMapLatest { it.connectionInfo }
+ .flatMapLatest { it.isEmergencyOnly }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
- initialValue = activeRepo.value.connectionInfo.value,
+ columnName = COL_EMERGENCY,
+ activeRepo.value.isEmergencyOnly.value
)
- .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.isEmergencyOnly.value
+ )
+
+ override val isRoaming =
+ activeRepo
+ .flatMapLatest { it.isRoaming }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_ROAMING,
+ activeRepo.value.isRoaming.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
+
+ override val operatorAlphaShort =
+ activeRepo
+ .flatMapLatest { it.operatorAlphaShort }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_OPERATOR,
+ activeRepo.value.operatorAlphaShort.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.operatorAlphaShort.value
+ )
+
+ override val isInService =
+ activeRepo
+ .flatMapLatest { it.isInService }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_IN_SERVICE,
+ activeRepo.value.isInService.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
+
+ override val isGsm =
+ activeRepo
+ .flatMapLatest { it.isGsm }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_GSM,
+ activeRepo.value.isGsm.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
+
+ override val cdmaLevel =
+ activeRepo
+ .flatMapLatest { it.cdmaLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CDMA_LEVEL,
+ activeRepo.value.cdmaLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
+
+ override val primaryLevel =
+ activeRepo
+ .flatMapLatest { it.primaryLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_PRIMARY_LEVEL,
+ activeRepo.value.primaryLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
+
+ override val dataConnectionState =
+ activeRepo
+ .flatMapLatest { it.dataConnectionState }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.dataConnectionState.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.dataConnectionState.value
+ )
+
+ override val dataActivityDirection =
+ activeRepo
+ .flatMapLatest { it.dataActivityDirection }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.dataActivityDirection.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.dataActivityDirection.value
+ )
+
+ override val carrierNetworkChangeActive =
+ activeRepo
+ .flatMapLatest { it.carrierNetworkChangeActive }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_NETWORK_CHANGE,
+ activeRepo.value.carrierNetworkChangeActive.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.carrierNetworkChangeActive.value
+ )
+
+ override val resolvedNetworkType =
+ activeRepo
+ .flatMapLatest { it.resolvedNetworkType }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.resolvedNetworkType.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.resolvedNetworkType.value
+ )
override val dataEnabled =
activeRepo
@@ -187,4 +319,15 @@
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
}
}
+
+ companion object {
+ const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_ROAMING = "roaming"
+ const val COL_OPERATOR = "operatorName"
+ const val COL_IS_IN_SERVICE = "isInService"
+ const val COL_IS_GSM = "isGsm"
+ const val COL_CDMA_LEVEL = "cdmaLevel"
+ const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index e182bc6..f1fc386 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.IntentFilter
import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
@@ -37,7 +38,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
@@ -49,6 +50,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -62,10 +64,10 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -165,83 +167,100 @@
}
.shareIn(scope, SharingStarted.WhileSubscribed())
- private fun updateConnectionState(
- prevState: MobileConnectionModel,
- callbackEvent: CallbackEvent,
- ): MobileConnectionModel =
- when (callbackEvent) {
- is CallbackEvent.OnServiceStateChanged -> {
- val serviceState = callbackEvent.serviceState
- prevState.copy(
- isEmergencyOnly = serviceState.isEmergencyOnly,
- isRoaming = serviceState.roaming,
- operatorAlphaShort = serviceState.operatorAlphaShort,
- isInService = Utils.isInService(serviceState),
- )
- }
- is CallbackEvent.OnSignalStrengthChanged -> {
- val signalStrength = callbackEvent.signalStrength
- val cdmaLevel =
- signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
- strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
-
- val primaryLevel = signalStrength.level
-
- prevState.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- }
- is CallbackEvent.OnDataConnectionStateChanged -> {
- prevState.copy(dataConnectionState = callbackEvent.dataState.toDataConnectionType())
- }
- is CallbackEvent.OnDataActivity -> {
- prevState.copy(
- dataActivityDirection = callbackEvent.direction.toMobileDataActivityModel()
- )
- }
- is CallbackEvent.OnCarrierNetworkChange -> {
- prevState.copy(carrierNetworkChangeActive = callbackEvent.active)
- }
- is CallbackEvent.OnDisplayInfoChanged -> {
- val telephonyDisplayInfo = callbackEvent.telephonyDisplayInfo
- val networkType =
- if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
- UnknownNetworkType
- } else if (
- telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
- ) {
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(telephonyDisplayInfo.networkType)
- )
- } else {
- OverrideNetworkType(
- mobileMappingsProxy.toIconKeyOverride(
- telephonyDisplayInfo.overrideNetworkType
- )
- )
- }
- prevState.copy(resolvedNetworkType = networkType)
- }
- is CallbackEvent.OnDataEnabledChanged -> {
- // Not part of this object, handled in a separate flow
- prevState
- }
- }
-
- override val connectionInfo = run {
- val initial = MobileConnectionModel()
+ override val isEmergencyOnly =
callbackEvents
- .scan(initial, ::updateConnectionState)
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.isEmergencyOnly }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isRoaming =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.roaming }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val operatorAlphaShort =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.operatorAlphaShort }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val isInService =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { Utils.isInService(it.serviceState) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isGsm =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map { it.signalStrength.isGsm }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val cdmaLevel =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map {
+ it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
+ strengths ->
+ if (strengths.isNotEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val primaryLevel =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map { it.signalStrength.level }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val dataConnectionState =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+ .map { it.dataState.toDataConnectionType() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
+
+ override val dataActivityDirection =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDataActivity>()
+ .map { it.direction.toMobileDataActivityModel() }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
+
+ override val carrierNetworkChangeActive =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+ .map { it.active }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val resolvedNetworkType =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+ .map {
+ if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+ UnknownNetworkType
+ } else if (
+ it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(
+ mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+ )
+ } else {
+ OverrideNetworkType(
+ mobileMappingsProxy.toIconKeyOverride(
+ it.telephonyDisplayInfo.overrideNetworkType
+ )
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
override val numberOfLevels =
systemUiCarrierConfig.shouldInflateSignalStrength
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index f97e41c..b7da3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -266,6 +266,7 @@
val callback =
object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onLost(network: Network) {
+ logger.logOnLost(network, isDefaultNetworkCallback = true)
// Send a disconnected model when lost. Maybe should create a sealed
// type or null here?
trySend(MobileConnectivityModel())
@@ -275,6 +276,11 @@
network: Network,
caps: NetworkCapabilities
) {
+ logger.logOnCapabilitiesChanged(
+ network,
+ caps,
+ isDefaultNetworkCallback = true,
+ )
trySend(
MobileConnectivityModel(
isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4caf2b0..7df6764 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -34,6 +34,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -133,11 +134,9 @@
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- private val connectionInfo = connectionRepository.connectionInfo
-
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
- override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
+ override val activity = connectionRepository.dataActivityDirection
override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
@@ -155,11 +154,11 @@
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
override val networkName =
- combine(connectionInfo, connectionRepository.networkName) { connection, networkName ->
- if (
- networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
- ) {
- NetworkNameModel.IntentDerived(connection.operatorAlphaShort)
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ NetworkNameModel.IntentDerived(operatorAlphaShort)
} else {
networkName
}
@@ -173,19 +172,19 @@
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- connectionInfo,
+ connectionRepository.resolvedNetworkType,
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefault,
- ) { info, mapping, defaultGroup, isDefault ->
+ ) { resolvedNetworkType, mapping, defaultGroup, isDefault ->
if (!isDefault) {
return@combine NOT_DEFAULT_DATA
}
- when (info.resolvedNetworkType) {
+ when (resolvedNetworkType) {
is ResolvedNetworkType.CarrierMergedNetworkType ->
- info.resolvedNetworkType.iconGroupOverride
- else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
+ resolvedNetworkType.iconGroupOverride
+ else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
.distinctUntilChanged()
@@ -200,17 +199,19 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
- override val isEmergencyOnly: StateFlow<Boolean> =
- connectionInfo
- .mapLatest { it.isEmergencyOnly }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isEmergencyOnly = connectionRepository.isEmergencyOnly
override val isRoaming: StateFlow<Boolean> =
- combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
- if (connection.carrierNetworkChangeActive) {
+ combine(
+ connectionRepository.carrierNetworkChangeActive,
+ connectionRepository.isGsm,
+ connectionRepository.isRoaming,
+ connectionRepository.cdmaRoaming,
+ ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
+ if (carrierNetworkChangeActive) {
false
- } else if (connection.isGsm) {
- connection.isRoaming
+ } else if (isGsm) {
+ isRoaming
} else {
cdmaRoaming
}
@@ -218,12 +219,17 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val level: StateFlow<Int> =
- combine(connectionInfo, alwaysUseCdmaLevel) { connection, alwaysUseCdmaLevel ->
+ combine(
+ connectionRepository.isGsm,
+ connectionRepository.primaryLevel,
+ connectionRepository.cdmaLevel,
+ alwaysUseCdmaLevel,
+ ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
when {
// GSM connections should never use the CDMA level
- connection.isGsm -> connection.primaryLevel
- alwaysUseCdmaLevel -> connection.cdmaLevel
- else -> connection.primaryLevel
+ isGsm -> primaryLevel
+ alwaysUseCdmaLevel -> cdmaLevel
+ else -> primaryLevel
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -236,12 +242,9 @@
)
override val isDataConnected: StateFlow<Boolean> =
- connectionInfo
- .mapLatest { connection -> connection.dataConnectionState == Connected }
+ connectionRepository.dataConnectionState
+ .map { it == Connected }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isInService =
- connectionRepository.connectionInfo
- .mapLatest { it.isInService }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isInService = connectionRepository.isInService
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 6f29e33..a96e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -38,11 +38,24 @@
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
- { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" }
+ { "on${if (bool1) "Default" else ""}CapabilitiesChanged: net=$int1 capabilities=$str1" }
)
}
- fun logOnLost(buffer: LogBuffer, tag: String, network: Network) {
- buffer.log(tag, LogLevel.INFO, { int1 = network.getNetId() }, { "onLost: net=$int1" })
+ fun logOnLost(
+ buffer: LogBuffer,
+ tag: String,
+ network: Network,
+ isDefaultNetworkCallback: Boolean,
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ bool1 = isDefaultNetworkCallback
+ },
+ { "on${if (bool1) "Default" else ""}Lost: net=$int1" }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index ee58160..b5e7b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -128,6 +128,7 @@
}
override fun onLost(network: Network) {
+ logger.logOnLost(network, isDefaultNetworkCallback = true)
// The system no longer has a default network, so wifi is definitely not
// default.
trySend(false)
@@ -179,7 +180,7 @@
}
override fun onLost(network: Network) {
- logger.logOnLost(network)
+ logger.logOnLost(network, isDefaultNetworkCallback = false)
wifiNetworkChangeEvents.tryEmit(Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index a32e475..bb0b166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -48,8 +48,8 @@
)
}
- fun logOnLost(network: Network) {
- LoggerHelper.logOnLost(buffer, TAG, network)
+ fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+ LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logIntent(intentName: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 3f895ad..02d4479 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -32,6 +32,8 @@
import android.provider.Settings
import android.util.Log
import com.android.internal.util.UserIcons
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
@@ -90,6 +92,7 @@
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
broadcastDispatcher: BroadcastDispatcher,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val activityManager: ActivityManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
@@ -286,6 +289,12 @@
val isSimpleUserSwitcher: Boolean
get() = repository.isSimpleUserSwitcher()
+ val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardGoingAway() {
+ dismissDialog()
+ }
+ }
init {
refreshUsersScheduler.refreshIfNotPaused()
@@ -316,6 +325,7 @@
onBroadcastReceived(intent, previousSelectedUser)
}
.launchIn(applicationScope)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
}
fun addCallback(callback: UserCallback) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b847d67..1cfcf8c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1473,6 +1473,7 @@
mHandler.removeMessages(H.SHOW);
if (mIsAnimatingDismiss) {
Log.d(TAG, "dismissH: isAnimatingDismiss");
+ Trace.endSection();
return;
}
mIsAnimatingDismiss = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index bbc7bc9..4dc4c2c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -519,6 +519,38 @@
}
@Test
+ public void testWillRunDismissFromKeyguardIsTrue() {
+ ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+ when(action.willRunAnimationOnKeyguard()).thenReturn(true);
+ mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
+ }
+
+ @Test
+ public void testWillRunDismissFromKeyguardIsFalse() {
+ ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+ when(action.willRunAnimationOnKeyguard()).thenReturn(false);
+ mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+ }
+
+ @Test
+ public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+ mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
+ null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+ }
+
+ @Test
public void testOnStartingToHide() {
mKeyguardSecurityContainerController.onStartingToHide();
verify(mInputViewController).onStartingToHide();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 531006d..565fc57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -263,9 +263,6 @@
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
- assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
- getContext().getResources().getDimensionPixelSize(
- R.dimen.bouncer_user_switcher_y_trans));
assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3c80dad..4c92edd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,7 +17,6 @@
package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
@@ -2200,6 +2199,32 @@
}
@Test
+ public void testPostureChangeToUnsupported_stopsFaceListeningState() {
+ // GIVEN device is listening for face
+ mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED;
+ deviceInPostureStateClosed();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+
+ verifyFaceAuthenticateCall();
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN device is opened
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+
+ // THEN face listening is stopped.
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE));
+ }
+
+ @Test
public void testShouldListenForFace_withLockedDown_returnsFalse()
throws RemoteException {
keyguardNotGoingAway();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index f01da2d..8a5c5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -61,7 +61,7 @@
val interp = FontInterpolator()
assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f))
assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f))
- assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
}
@Test
@@ -74,7 +74,7 @@
.build()
val interp = FontInterpolator()
- assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 28e80057..c98d537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserFileManager
@@ -56,7 +57,6 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
@@ -66,6 +66,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.io.File
import java.util.*
@@ -108,6 +109,8 @@
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+ private val preferredPanelRepository = FakeSelectedComponentRepository()
+
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
private lateinit var canceller: DidRunRunnable
@@ -168,6 +171,7 @@
wrapper,
delayableExecutor,
uiController,
+ preferredPanelRepository,
bindingController,
listingController,
userFileManager,
@@ -221,6 +225,7 @@
mContext,
delayableExecutor,
uiController,
+ preferredPanelRepository,
bindingController,
listingController,
userFileManager,
@@ -240,6 +245,7 @@
mContext,
delayableExecutor,
uiController,
+ preferredPanelRepository,
bindingController,
listingController,
userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 7ac1953..272f589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -22,6 +22,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.FakeSharedPreferences
@@ -40,6 +42,8 @@
@Mock private lateinit var userTracker: UserTracker
+ private val featureFlags = FakeFeatureFlags()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -48,6 +52,7 @@
arrayOf<String>()
)
whenever(userTracker.userId).thenReturn(0)
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
}
@Test
@@ -127,8 +132,25 @@
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
+ @Test
+ fun testSetAuthorizedPackageAfterFeatureDisabled() {
+ mContext.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE)
+ )
+ val sharedPrefs = FakeSharedPreferences()
+ val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+ val repository = createRepository(fileManager)
+
+ repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+
+ assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE))
+ }
+
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
- return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
+ return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags)
}
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
new file mode 100644
index 0000000..a7677cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+class FakeSelectedComponentRepository : SelectedComponentRepository {
+
+ private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
+ private var shouldAddDefaultPanel: Boolean = true
+
+ override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
+ selectedComponent
+
+ override fun setSelectedComponent(
+ selectedComponent: SelectedComponentRepository.SelectedComponent
+ ) {
+ this.selectedComponent = selectedComponent
+ }
+
+ override fun removeSelectedComponent() {
+ selectedComponent = null
+ }
+
+ override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
+
+ override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
+ shouldAddDefaultPanel = shouldAdd
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
new file mode 100644
index 0000000..0c7b9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SelectedComponentRepositoryTest : SysuiTestCase() {
+
+ private companion object {
+ val COMPONENT_A =
+ SelectedComponentRepository.SelectedComponent(
+ name = "a",
+ componentName = ComponentName.unflattenFromString("pkg/.cls_a"),
+ isPanel = false,
+ )
+ val COMPONENT_B =
+ SelectedComponentRepository.SelectedComponent(
+ name = "b",
+ componentName = ComponentName.unflattenFromString("pkg/.cls_b"),
+ isPanel = false,
+ )
+ }
+
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userFileManager: UserFileManager
+
+ private val featureFlags = FakeFeatureFlags()
+ private val sharedPreferences: SharedPreferences = FakeSharedPreferences()
+
+ // under test
+ private lateinit var repository: SelectedComponentRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userFileManager.getSharedPreferences(any(), any(), any()))
+ .thenReturn(sharedPreferences)
+
+ repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
+ }
+
+ @Test
+ fun testUnsetIsNull() {
+ assertThat(repository.getSelectedComponent()).isNull()
+ }
+
+ @Test
+ fun testGetReturnsSet() {
+ repository.setSelectedComponent(COMPONENT_A)
+
+ assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_A)
+ }
+
+ @Test
+ fun testSetOverrides() {
+ repository.setSelectedComponent(COMPONENT_A)
+ repository.setSelectedComponent(COMPONENT_B)
+
+ assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_B)
+ }
+
+ @Test
+ fun testRemove() {
+ repository.setSelectedComponent(COMPONENT_A)
+
+ repository.removeSelectedComponent()
+
+ assertThat(repository.getSelectedComponent()).isNull()
+ }
+
+ @Test
+ fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+
+ assertThat(repository.shouldAddDefaultComponent()).isTrue()
+ }
+
+ @Test
+ fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+
+ assertThat(repository.shouldAddDefaultComponent()).isTrue()
+ }
+
+ @Test
+ fun testFeatureEnabled_shouldAddDefaultPanelChecked() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+ repository.setShouldAddDefaultComponent(false)
+
+ assertThat(repository.shouldAddDefaultComponent()).isFalse()
+ }
+
+ @Test
+ fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+ repository.setShouldAddDefaultComponent(false)
+
+ assertThat(repository.shouldAddDefaultComponent()).isTrue()
+ }
+
+ @Test
+ fun testGetPreferredStructure_differentUserId() {
+ sharedPreferences.savePanel(COMPONENT_A)
+ whenever(
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ 0,
+ 1,
+ )
+ )
+ .thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })
+
+ val previousPreferredStructure = repository.getSelectedComponent()
+ whenever(userTracker.userId).thenReturn(1)
+ val currentPreferredStructure = repository.getSelectedComponent()
+
+ assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
+ assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+ assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
+ }
+
+ private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
+ edit()
+ .putString("controls_component", panel.componentName?.flattenToString())
+ .putString("controls_structure", panel.name)
+ .putBoolean("controls_is_panel", panel.isPanel)
+ .commit()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 7ecaca6..9d8084d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -23,17 +23,19 @@
import android.content.pm.ServiceInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import java.util.Optional
import org.junit.Before
@@ -53,16 +55,16 @@
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
@Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+
+ private val preferredPanelsRepository = FakeSelectedComponentRepository()
private lateinit var fakeExecutor: FakeExecutor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf<String>()
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
fakeExecutor = FakeExecutor(FakeSystemClock())
}
@@ -87,10 +89,8 @@
@Test
fun testPreferredPackagesNotInstalled_noNewSelection() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
`when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
@@ -101,10 +101,8 @@
@Test
fun testPreferredPackageNotPanel_noNewSelection() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -116,10 +114,8 @@
@Test
fun testExistingSelection_noNewSelection() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection())
.thenReturn(mock<SelectedItem.PanelItem>())
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
@@ -132,10 +128,8 @@
@Test
fun testPanelAdded() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -147,10 +141,8 @@
@Test
fun testMultiplePreferredOnlyOnePanel_panelAdded() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf("other_package", TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
@@ -166,10 +158,8 @@
@Test
fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL, "other_package")
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
@@ -217,6 +207,20 @@
verify(controlsController, never()).bindComponentForPanel(any())
}
+ @Test
+ fun testAlreadyAddedPanel_noNewSelection() {
+ preferredPanelsRepository.setShouldAddDefaultComponent(false)
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
private fun createStartable(enabled: Boolean): ControlsStartable {
val component: ControlsComponent =
mock() {
@@ -230,7 +234,13 @@
`when`(getControlsListingController()).thenReturn(Optional.empty())
}
}
- return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
+ return ControlsStartable(
+ fakeExecutor,
+ component,
+ userTracker,
+ authorizedPanelsRepository,
+ preferredPanelsRepository,
+ )
}
private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 23faa99..3f61bf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -42,16 +42,14 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.FakeSystemUIDialogController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -64,20 +62,18 @@
import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -87,11 +83,9 @@
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@Mock lateinit var activityStarter: ActivityStarter
- @Mock lateinit var shadeController: ShadeController
@Mock lateinit var iconCache: CustomIconCache
@Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
@Mock lateinit var keyguardStateController: KeyguardStateController
- @Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var dumpManager: DumpManager
@@ -99,7 +93,7 @@
@Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var packageManager: PackageManager
- private val sharedPreferences = FakeSharedPreferences()
+ private val preferredPanelRepository = FakeSelectedComponentRepository()
private val fakeDialogController = FakeSystemUIDialogController()
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val bgExecutor = FakeExecutor(FakeSystemClock())
@@ -138,94 +132,30 @@
iconCache,
controlsMetricsLogger,
keyguardStateController,
- userFileManager,
userTracker,
Optional.of(taskViewFactory),
controlsSettingsRepository,
authorizedPanelsRepository,
+ preferredPanelRepository,
featureFlags,
ControlsDialogsFactory { fakeDialogController.dialog },
dumpManager,
)
- `when`(
- userFileManager.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- 0,
- 0
- )
- )
- .thenReturn(sharedPreferences)
- `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
- .thenReturn(sharedPreferences)
`when`(userTracker.userId).thenReturn(0)
`when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
}
@Test
- fun testGetPreferredStructure() {
- val structureInfo = mock<StructureInfo>()
- underTest.getPreferredSelectedItem(listOf(structureInfo))
- verify(userFileManager)
- .getSharedPreferences(
- fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- mode = 0,
- userId = 0
- )
- }
-
- @Test
- fun testGetPreferredStructure_differentUserId() {
- val selectedItems =
- listOf(
- SelectedItem.StructureItem(
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
- ),
- SelectedItem.StructureItem(
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
- ),
- )
- val structures = selectedItems.map { it.structure }
- sharedPreferences
- .edit()
- .putString("controls_component", selectedItems[0].componentName.flattenToString())
- .putString("controls_structure", selectedItems[0].name.toString())
- .commit()
-
- val differentSharedPreferences = FakeSharedPreferences()
- differentSharedPreferences
- .edit()
- .putString("controls_component", selectedItems[1].componentName.flattenToString())
- .putString("controls_structure", selectedItems[1].name.toString())
- .commit()
-
- val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
-
- `when`(
- userFileManager.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- 0,
- 1
- )
- )
- .thenReturn(differentSharedPreferences)
- `when`(userTracker.userId).thenReturn(1)
-
- val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
-
- assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
- assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
- assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
- }
-
- @Test
fun testGetPreferredPanel() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
- sharedPreferences
- .edit()
- .putString("controls_component", panel.componentName.flattenToString())
- .putString("controls_structure", panel.appName.toString())
- .putBoolean("controls_is_panel", true)
- .commit()
+
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(
+ name = panel.appName.toString(),
+ componentName = panel.componentName,
+ isPanel = true,
+ )
+ )
val selected = underTest.getPreferredSelectedItem(emptyList())
@@ -369,11 +299,9 @@
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
),
)
- sharedPreferences
- .edit()
- .putString("controls_component", selectedItems[0].componentName.flattenToString())
- .putString("controls_structure", selectedItems[0].name.toString())
- .commit()
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(selectedItems[0])
+ )
assertThat(underTest.resolveActivity())
.isEqualTo(ControlsProviderSelectorActivity::class.java)
@@ -418,12 +346,9 @@
val componentName = ComponentName(context, "cls")
whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
val panel = SelectedItem.PanelItem("App name", componentName)
- sharedPreferences
- .edit()
- .putString("controls_component", panel.componentName.flattenToString())
- .putString("controls_structure", panel.appName.toString())
- .putBoolean("controls_is_panel", true)
- .commit()
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(panel)
+ )
underTest.show(parent, {}, context)
underTest.startRemovingApp(componentName, "Test App")
@@ -432,11 +357,8 @@
verify(controlsController).removeFavorites(eq(componentName))
assertThat(underTest.getPreferredSelectedItem(emptyList()))
.isEqualTo(SelectedItem.EMPTY_SELECTION)
- with(sharedPreferences) {
- assertThat(contains("controls_component")).isFalse()
- assertThat(contains("controls_structure")).isFalse()
- assertThat(contains("controls_is_panel")).isFalse()
- }
+ assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isFalse()
+ assertThat(preferredPanelRepository.getSelectedComponent()).isNull()
}
@Test
@@ -452,12 +374,9 @@
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName(context, "activity")
- sharedPreferences
- .edit()
- .putString("controls_component", panel.componentName.flattenToString())
- .putString("controls_structure", panel.appName.toString())
- .putBoolean("controls_is_panel", true)
- .commit()
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(panel)
+ )
return ControlsServiceInfo(panel.componentName, panel.appName, activity)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 6c23254..0a94706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -1,6 +1,8 @@
package com.android.systemui.dreams
+import android.animation.Animator
import android.animation.AnimatorSet
+import android.animation.ValueAnimator
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
@@ -10,13 +12,16 @@
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
@@ -71,6 +76,19 @@
}
@Test
+ fun testExitAnimationUpdatesState() {
+ controller.startExitAnimations(animatorBuilder = { mockAnimator })
+
+ verify(stateController).setExitAnimationsRunning(true)
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+
+ captor.value.onAnimationEnd(mockAnimator)
+ verify(stateController).setExitAnimationsRunning(false)
+ }
+
+ @Test
fun testWakeUpCallsExecutor() {
val mockExecutor: DelayableExecutor = mock()
val mockCallback: Runnable = mock()
@@ -87,7 +105,7 @@
fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
- controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
+ controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
@@ -100,4 +118,50 @@
// animator.
verify(mockStartAnimator, times(1)).cancel()
}
+
+ @Test
+ fun testEntryAnimations_translatesUpwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ false,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
+
+ @Test
+ fun testEntryAnimations_translatesDownwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ true,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
}
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 6b095ff..2a72e7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +34,7 @@
import androidx.test.filters.SmallTest;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
@@ -65,6 +67,9 @@
DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
@Mock
+ LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+
+ @Mock
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
@@ -109,6 +114,7 @@
mComplicationHostViewController,
mDreamOverlayContentView,
mDreamOverlayStatusBarViewController,
+ mLowLightTransitionCoordinator,
mBlurUtils,
mHandler,
mResources,
@@ -200,7 +206,7 @@
mController.onViewAttached();
- verify(mAnimationsController).startEntryAnimations();
+ verify(mAnimationsController).startEntryAnimations(false);
verify(mAnimationsController, never()).cancelAnimations();
}
@@ -210,11 +216,11 @@
mController.onViewAttached();
- verify(mAnimationsController, never()).startEntryAnimations();
+ verify(mAnimationsController, never()).startEntryAnimations(anyBoolean());
}
@Test
- public void testSkipEntryAnimationsWhenExitingLowLight() {
+ public void testDownwardEntryAnimationsWhenExitingLowLight() {
ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
when(mStateController.isLowLightActive()).thenReturn(false);
@@ -230,8 +236,14 @@
mController.onViewAttached();
// Entry animations should be started then immediately ended to skip to the end.
- verify(mAnimationsController).startEntryAnimations();
- verify(mAnimationsController).endAnimations();
+ verify(mAnimationsController).startEntryAnimations(true);
+ }
+
+ @Test
+ public void testStartsExitAnimationsBeforeEnteringLowLight() {
+ mController.onBeforeEnterLowLight();
+
+ verify(mAnimationsController).startExitAnimations();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
new file mode 100644
index 0000000..0e14591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class ConditionalRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: ConditionalRestarter
+
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ val restartDelayMs = 0L
+ val dispatcher = StandardTestDispatcher()
+ val testScope = TestScope(dispatcher)
+
+ val conditionA = FakeCondition()
+ val conditionB = FakeCondition()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ ConditionalRestarter(
+ systemExitRestarter,
+ setOf(conditionA, conditionB),
+ restartDelayMs,
+ testScope,
+ dispatcher
+ )
+ }
+
+ @Test
+ fun restart_ImmediatelySatisfied() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = true
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForConditionA() =
+ testScope.runTest {
+ conditionA.canRestart = false
+ conditionB.canRestart = true
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionA.canRestart = true
+ conditionA.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForConditionB() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = false
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionB.canRestart = true
+ conditionB.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForAllConditions() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = false
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ // B becomes true, but A is now false
+ conditionA.canRestart = false
+ conditionB.canRestart = true
+ conditionB.retryFn?.invoke()
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionA.canRestart = true
+ conditionA.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ class FakeCondition : ConditionalRestarter.Condition {
+ var retryFn: (() -> Unit)? = null
+ var canRestart = false
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ this.retryFn = retryFn
+
+ return canRestart
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
deleted file mode 100644
index 6060afe..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.flags
-
-import android.test.suitebuilder.annotation.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-/**
- * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
- */
-@SmallTest
-class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
- private lateinit var restarter: FeatureFlagsReleaseRestarter
-
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var batteryController: BatteryController
- @Mock private lateinit var systemExitRestarter: SystemExitRestarter
- private val executor = FakeExecutor(FakeSystemClock())
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- restarter =
- FeatureFlagsReleaseRestarter(
- wakefulnessLifecycle,
- batteryController,
- executor,
- systemExitRestarter
- )
- }
-
- @Test
- fun testRestart_ScheduledWhenReady() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testRestart_RestartsWhenIdle() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
- executor.advanceClockToLast()
- executor.runAllReady()
- verify(systemExitRestarter).restartSystemUI(any())
- }
-
- @Test
- fun testRestart_NotScheduledWhenAwake() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(0)
- }
-
- @Test
- fun testRestart_NotScheduledWhenNotPluggedIn() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(false)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(0)
- }
-
- @Test
- fun testRestart_NotDoubleSheduled() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testWakefulnessLifecycle_CanRestart() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- whenever(batteryController.isPluggedIn).thenReturn(true)
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
-
- val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
- verify(wakefulnessLifecycle).addObserver(captor.capture())
-
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-
- captor.value.onFinishedGoingToSleep()
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testBatteryController_CanRestart() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(false)
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
-
- val captor =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
- verify(batteryController).addCallback(captor.capture())
-
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- captor.value.onBatteryLevelChanged(0, true, true)
- assertThat(executor.numPending()).isEqualTo(1)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
new file mode 100644
index 0000000..647b05a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.BatteryController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class PluggedInConditionTest : SysuiTestCase() {
+ private lateinit var condition: PluggedInCondition
+
+ @Mock private lateinit var batteryController: BatteryController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ condition = PluggedInCondition(batteryController)
+ }
+
+ @Test
+ fun testCondition_unplugged() {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(condition.canRestartNow({})).isFalse()
+ }
+
+ @Test
+ fun testCondition_pluggedIn() {
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(condition.canRestartNow({})).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ var retried = false
+ val retryFn = { retried = true }
+
+ // No restart yet, but we do register a listener now.
+ assertThat(condition.canRestartNow(retryFn)).isFalse()
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(retried).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 686782f..f7a773e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -20,12 +20,11 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -34,37 +33,45 @@
* Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
*/
@SmallTest
-class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
- private lateinit var restarter: FeatureFlagsDebugRestarter
+class ScreenIdleConditionTest : SysuiTestCase() {
+ private lateinit var condition: ScreenIdleCondition
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var systemExitRestarter: SystemExitRestarter
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ condition = ScreenIdleCondition(wakefulnessLifecycle)
}
@Test
- fun testRestart_ImmediateWhenAsleep() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter).restartSystemUI(any())
- }
-
- @Test
- fun testRestart_WaitsForSceenOff() {
+ fun testCondition_awake() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter, never()).restartSystemUI(any())
+ assertThat(condition.canRestartNow {}).isFalse()
+ }
+ @Test
+ fun testCondition_asleep() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+ assertThat(condition.canRestartNow {}).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ var retried = false
+ val retryFn = { retried = true }
+
+ // No restart yet, but we do register a listener now.
+ assertThat(condition.canRestartNow(retryFn)).isFalse()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
- captor.value.onFinishedGoingToSleep()
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- verify(systemExitRestarter).restartSystemUI(any())
+ captor.value.onFinishedGoingToSleep()
+ assertThat(retried).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 62c9e5f..5528b94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -281,6 +281,24 @@
}
@Test
+ fun `quickAffordance - hidden when quick settings is visible`() =
+ testScope.runTest {
+ repository.setQuickSettingsVisible(true)
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
+ )
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
+
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
+
+ @Test
fun `quickAffordance - bottom start affordance hidden while dozing`() =
testScope.runTest {
repository.setDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index fe9098f..fc3a638 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -967,6 +967,92 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun `OCCLUDED to GONE`() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ // AND occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to GONE should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `OCCLUDED to LOCKSCREEN`() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to LOCKSCREEN should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun startingToWake() =
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 2a91799..746f668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -21,7 +21,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -44,21 +46,86 @@
private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardTransitionRepository()
val interactor = KeyguardTransitionInteractor(repository)
- underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController)
+ underTest =
+ PrimaryBouncerToGoneTransitionViewModel(
+ interactor,
+ statusBarStateController,
+ primaryBouncerInteractor
+ )
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
}
@Test
+ fun bouncerAlpha() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun bouncerAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach { assertThat(it).isEqualTo(0f) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun scrimAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<ScrimAlpha>()
+
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
fun scrimBehindAlpha_leaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
+ val values = mutableListOf<ScrimAlpha>()
- val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
@@ -68,7 +135,9 @@
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isEqualTo(1f) }
+ values.forEach {
+ assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+ }
job.cancel()
}
@@ -76,9 +145,9 @@
@Test
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
+ val values = mutableListOf<ScrimAlpha>()
- val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
@@ -88,8 +157,10 @@
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
- assertThat(values[3]).isEqualTo(0f)
+ values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[3].behindAlpha).isEqualTo(0f)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index ab0669a..d428db7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -2031,7 +2031,7 @@
}
@Test
- fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+ fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
addPlaybackStateAction()
@@ -2051,6 +2051,40 @@
}
@Test
+ fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ addPlaybackStateAction()
+
+ // When a media control using session actions and that does allow resumption is added,
+ addNotificationAndLoad()
+ val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+ mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+ // And then the session is destroyed without timing out first
+ sessionCallbackCaptor.value.invoke(KEY)
+
+ // It is converted to a resume player
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ verify(logger)
+ .logActiveConvertedToResume(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId)
+ )
+ }
+
+ @Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 4dfa626..9ab7289 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -313,6 +313,25 @@
}
@Test
+ fun testOnLoadTwice_onlyChecksOnce() {
+ // When data is first loaded,
+ setUpMbsWithValidResolveInfo()
+ resumeListener.onMediaDataLoaded(KEY, null, data)
+
+ // We notify the manager to set a null action
+ verify(mediaDataManager).setResumeAction(KEY, null)
+
+ // If we then get another update from the app before the first check completes
+ assertThat(executor.numPending()).isEqualTo(1)
+ var dataWithCheck = data.copy(hasCheckedForResume = true)
+ resumeListener.onMediaDataLoaded(KEY, null, dataWithCheck)
+
+ // We do not try to start another check
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(mediaDataManager).setResumeAction(KEY, null)
+ }
+
+ @Test
fun testOnUserUnlock_loadsTracks() {
// Set up mock service to successfully find valid media
val description = MediaDescription.Builder().setTitle(TITLE).build()
@@ -392,7 +411,7 @@
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
}
- verify(sharedPrefsEditor, times(1)).apply()
+ verify(sharedPrefsEditor).apply()
}
@Test
@@ -432,8 +451,8 @@
resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
// We add its resume controls
- verify(resumeBrowser, times(1)).findRecentMedia()
- verify(mediaDataManager, times(1))
+ verify(resumeBrowser).findRecentMedia()
+ verify(mediaDataManager)
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
}
@@ -516,7 +535,7 @@
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
}
- verify(sharedPrefsEditor, times(1)).apply()
+ verify(sharedPrefsEditor).apply()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 89606bf..0ab0e2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -52,6 +52,8 @@
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -60,6 +62,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -103,6 +106,8 @@
@Mock private QSSquishinessController mSquishinessController;
@Mock private FooterActionsViewModel mFooterActionsViewModel;
@Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+ @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ @Mock private FeatureFlags mFeatureFlags;
private View mQsFragmentView;
public QSFragmentTest() {
@@ -148,8 +153,9 @@
}
@Test
- public void transitionToFullShade_setsAlphaUsingShadeInterpolator() {
+ public void transitionToFullShade_smallScreen_alphaAlways1() {
QSFragment fragment = resumeAndGetFragment();
+ setIsSmallScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -158,6 +164,43 @@
fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
+ assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f);
+ }
+
+ @Test
+ public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true);
+ QSFragment fragment = resumeAndGetFragment();
+ setIsLargeScreen();
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+ boolean isTransitioningToFullShade = true;
+ float transitionProgress = 0.5f;
+ float squishinessFraction = 0.5f;
+ when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
+ assertThat(mQsFragmentView.getAlpha())
+ .isEqualTo(123f);
+ }
+
+ @Test
+ public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false);
+ QSFragment fragment = resumeAndGetFragment();
+ setIsLargeScreen();
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+ boolean isTransitioningToFullShade = true;
+ float transitionProgress = 0.5f;
+ float squishinessFraction = 0.5f;
+ when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
assertThat(mQsFragmentView.getAlpha())
.isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
}
@@ -514,7 +557,9 @@
mock(DumpManager.class),
mock(QSLogger.class),
mock(FooterActionsController.class),
- mFooterActionsViewModelFactory);
+ mFooterActionsViewModelFactory,
+ mLargeScreenShadeInterpolator,
+ mFeatureFlags);
}
private void setUpOther() {
@@ -622,4 +667,12 @@
return null;
}).when(view).getLocationOnScreen(any(int[].class));
}
+
+ private void setIsLargeScreen() {
+ getFragment().setIsNotificationPanelFullWidth(false);
+ }
+
+ private void setIsSmallScreen() {
+ getFragment().setIsNotificationPanelFullWidth(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 4f469f7..2dfb6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,8 +22,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,6 +33,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.annotation.IdRes;
import android.content.ContentResolver;
import android.content.res.Configuration;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
new file mode 100644
index 0000000..8309342
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
@@ -0,0 +1,144 @@
+package com.android.systemui.shade.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.google.common.truth.Expect
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeInterpolatorImplTest : SysuiTestCase() {
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val portraitShadeInterpolator = LargeScreenPortraitShadeInterpolator()
+ private val splitShadeInterpolator = SplitShadeInterpolator()
+ private val configurationController = FakeConfigurationController()
+ private val impl =
+ LargeScreenShadeInterpolatorImpl(
+ configurationController,
+ context,
+ splitShadeInterpolator,
+ portraitShadeInterpolator
+ )
+
+ @Test
+ fun getBehindScrimAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getBehindScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getBehindScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getBehindScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationScrimAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getNotificationScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationContentAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationContentAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationContentAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+ expected = { fraction ->
+ portraitShadeInterpolator.getNotificationContentAlpha(fraction)
+ }
+ )
+ }
+
+ @Test
+ fun getNotificationFooterAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationFooterAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getNotificationFooterAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+ expected = { fraction ->
+ portraitShadeInterpolator.getNotificationFooterAlpha(fraction)
+ }
+ )
+ }
+
+ @Test
+ fun getQsAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getQsAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getQsAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getQsAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getQsAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getQsAlpha(fraction) }
+ )
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ configurationController.notifyConfigurationChanged()
+ }
+
+ private fun assertInterpolation(
+ actual: (fraction: Float) -> Float,
+ expected: (fraction: Float) -> Float
+ ) {
+ for (i in 0..10) {
+ val fraction = i / 10f
+ expect.that(actual(fraction)).isEqualTo(expected(fraction))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
new file mode 100644
index 0000000..d24bcdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+class LinearLargeScreenShadeInterpolator : LargeScreenShadeInterpolator {
+ override fun getBehindScrimAlpha(fraction: Float) = fraction
+ override fun getNotificationScrimAlpha(fraction: Float) = fraction
+ override fun getNotificationContentAlpha(fraction: Float) = fraction
+ override fun getNotificationFooterAlpha(fraction: Float) = fraction
+ override fun getQsAlpha(fraction: Float) = fraction
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index 84f8656..cbf5485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -5,6 +5,8 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.STATE_CLOSED
import com.android.systemui.shade.STATE_OPEN
import com.android.systemui.shade.STATE_OPENING
@@ -30,6 +32,7 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var headsUpManager: HeadsUpManager
+ @Mock private lateinit var featureFlags: FeatureFlags
private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -45,7 +48,8 @@
scrimController,
context.resources,
statusBarStateController,
- headsUpManager)
+ headsUpManager,
+ featureFlags)
controller.onPanelStateChanged(STATE_OPENING)
}
@@ -107,6 +111,19 @@
}
@Test
+ fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.SHADE)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
whenever(statusBarStateController.currentOrUpcomingState)
.thenReturn(StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index cc45cf88..2eca78a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -57,7 +57,18 @@
clockView.measure(50, 50)
verify(mockTextAnimator).glyphFilter = any()
- verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 300,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = false,
+ duration = 350L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -68,8 +79,30 @@
clockView.animateAppearOnLockscreen()
verify(mockTextAnimator, times(2)).glyphFilter = any()
- verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
- verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 100,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = false,
+ duration = 0L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 300,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = true,
+ duration = 350L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
verifyNoMoreInteractions(mockTextAnimator)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 9e23d54..957b0f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -103,6 +103,7 @@
FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+ fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
}
@@ -402,17 +403,6 @@
}
@Test
- public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
- ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
-
- group.setBlockingHelperShowing(true);
- assertTrue(group.isBlockingHelperShowing());
-
- group.setBlockingHelperShowing(false);
- assertFalse(group.isBlockingHelperShowing());
- }
-
- @Test
public void testGetNumUniqueChildren_defaultChannel() throws Exception {
ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index d7ac6b4..3d8a744 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -117,7 +117,6 @@
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private NotificationListContainer mNotificationListContainer;
- @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
@Mock private OnSettingsClickListener mOnSettingsClickListener;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private CentralSurfaces mCentralSurfaces;
@@ -173,7 +172,6 @@
// Test doesn't support animation since the guts view is not attached.
doNothing().when(guts).openControls(
- eq(true) /* shouldDoCircularReveal */,
anyInt(),
anyInt(),
anyBoolean(),
@@ -190,7 +188,6 @@
assertEquals(View.INVISIBLE, guts.getVisibility());
mTestableLooper.processAllMessages();
verify(guts).openControls(
- eq(true),
anyInt(),
anyInt(),
anyBoolean(),
@@ -213,7 +210,6 @@
// Test doesn't support animation since the guts view is not attached.
doNothing().when(guts).openControls(
- eq(true) /* shouldDoCircularReveal */,
anyInt(),
anyInt(),
anyBoolean(),
@@ -237,7 +233,6 @@
assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
mTestableLooper.processAllMessages();
verify(guts).openControls(
- eq(true),
anyInt(),
anyInt(),
anyBoolean(),
@@ -379,7 +374,6 @@
public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
- row.setBlockingHelperShowing(false);
modifyRanking(row.getEntry())
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
.build();
@@ -414,7 +408,6 @@
public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
- row.setBlockingHelperShowing(true);
modifyRanking(row.getEntry())
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
index e696c87..fdfb4f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -76,7 +76,7 @@
fun openControls() {
guts.gutsContent = gutsContent
- guts.openControls(true, 0, 0, false, null)
+ guts.openControls(0, 0, false, null)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 87f4c32..09382ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -20,6 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
@@ -39,6 +41,8 @@
private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false }
private val bypassController = StackScrollAlgorithm.BypassController { false }
private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+ private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+ private val featureFlags = mock<FeatureFlags>()
private lateinit var sut: AmbientState
@@ -51,6 +55,8 @@
sectionProvider,
bypassController,
statusBarKeyguardViewManager,
+ largeScreenShadeInterpolator,
+ featureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 9d759c4..b1d3daa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -7,6 +7,9 @@
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.LegacySourceType
@@ -21,7 +24,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
/**
@@ -32,6 +37,9 @@
@RunWithLooper
class NotificationShelfTest : SysuiTestCase() {
+ @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
+ @Mock private lateinit var flags: FeatureFlags
+
private val shelf = NotificationShelf(
context,
/* attrs */ null,
@@ -50,8 +58,12 @@
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
+ whenever(ambientState.featureFlags).thenReturn(flags)
shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+ whenever(ambientState.isSmallScreen).thenReturn(true)
}
@Test
@@ -295,7 +307,35 @@
fun updateState_expansionChanging_shelfAlphaUpdated() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.6f,
- expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f)
+ expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f),
+ )
+ }
+
+ @Test
+ fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+ val expansionFraction = 0.6f
+ whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
+ whenever(ambientState.isSmallScreen).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(0.123f)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = 0.123f
+ )
+ }
+
+ @Test
+ fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
+ val expansionFraction = 0.6f
+ whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
+ whenever(ambientState.isSmallScreen).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(0.123f)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
)
}
@@ -305,7 +345,17 @@
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f)
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
+ )
+ }
+
+ @Test
+ fun updateState_largeScreen_expansionChangingWhileBouncerInTransit_bouncerInterpolatorUsed() {
+ whenever(ambientState.isBouncerInTransit).thenReturn(true)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = 0.95f,
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ff26a43..45ae96c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -52,7 +52,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -135,7 +134,6 @@
@Mock private StackStateLogger mStackLogger;
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
- @Mock private ShadeTransitionController mShadeTransitionController;
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Mock private SecureSettings mSecureSettings;
@@ -183,7 +181,6 @@
mNotifPipelineFlags,
mNotifCollection,
mLockscreenShadeTransitionController,
- mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index cbf841b..7153e59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -68,7 +68,9 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
@@ -129,6 +131,8 @@
@Mock private NotificationShelf mNotificationShelf;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ @Mock private FeatureFlags mFeatureFlags;
@Before
@UiThreadTest
@@ -142,7 +146,10 @@
mDumpManager,
mNotificationSectionsManager,
mBypassController,
- mStatusBarKeyguardViewManager));
+ mStatusBarKeyguardViewManager,
+ mLargeScreenShadeInterpolator,
+ mFeatureFlags
+ ));
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c..7f20f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -8,6 +8,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
@@ -15,11 +18,13 @@
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
@@ -30,12 +35,19 @@
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var expect: Expect = Expect.create()
+
+ private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
private val notificationRow = mock<ExpandableNotificationRow>()
private val dumpManager = mock<DumpManager>()
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
private val notificationShelf = mock<NotificationShelf>()
+ private val featureFlags = mock<FeatureFlags>()
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
}
@@ -44,8 +56,10 @@
dumpManager,
/* sectionProvider */ { _, _ -> false },
/* bypassController */ { false },
- mStatusBarKeyguardViewManager
- )
+ mStatusBarKeyguardViewManager,
+ largeScreenShadeInterpolator,
+ featureFlags,
+ )
private val testableResources = mContext.getOrCreateTestableResources()
@@ -59,6 +73,7 @@
fun setUp() {
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+ ambientState.isSmallScreen = true
hostView.addView(notificationRow)
}
@@ -145,11 +160,46 @@
}
@Test
- fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
+ fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+ val expansionFraction = 0.6f
+ val surfaceAlpha = 123f
+ ambientState.isSmallScreen = false
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(surfaceAlpha)
+
+ resetViewStates_expansionChanging_notificationAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = surfaceAlpha,
+ )
+ }
+
+ @Test
+ fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
+ val expansionFraction = 0.6f
+ val surfaceAlpha = 123f
+ ambientState.isSmallScreen = false
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(surfaceAlpha)
+
+ resetViewStates_expansionChanging_notificationAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = getContentAlpha(expansionFraction),
+ )
+ }
+
+ @Test
+ fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
+ ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f)
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
@@ -696,7 +746,7 @@
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
- expectedAlpha: Float
+ expectedAlpha: Float,
) {
ambientState.isExpansionChanging = true
ambientState.expansionFraction = expansionFraction
@@ -704,7 +754,7 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- assertThat(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
+ expect.that(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index e1fba81..7a1270f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -24,8 +24,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -41,6 +39,8 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.animation.Animator;
import android.app.AlarmManager;
import android.graphics.Color;
@@ -59,6 +59,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
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.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
@@ -67,7 +69,8 @@
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,6 +107,8 @@
private final FakeConfigurationController mConfigurationController =
new FakeConfigurationController();
+ private final LargeScreenShadeInterpolator
+ mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
private ScrimController mScrimController;
private ScrimView mScrimBehind;
@@ -128,11 +133,11 @@
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private CoroutineDispatcher mMainDispatcher;
- @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private FeatureFlags mFeatureFlags;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -242,20 +247,28 @@
when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
.thenReturn(emptyFlow());
- when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha())
+ when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
- mScrimController = new ScrimController(mLightBarController,
- mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
- new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
- mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+ mScrimController = new ScrimController(
+ mLightBarController,
+ mDozeParameters,
+ mAlarmManager,
+ mKeyguardStateController,
+ mDelayedWakeLockBuilder,
+ new FakeHandler(mLooper.getLooper()),
+ mKeyguardUpdateMonitor,
+ mDockManager,
+ mConfigurationController,
+ new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
- mSysuiStatusBarStateController,
- mMainDispatcher);
+ mMainDispatcher,
+ mLinearLargeScreenShadeInterpolator,
+ mFeatureFlags);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -653,7 +666,81 @@
}
@Test
- public void transitionToUnlocked() {
+ public void transitionToUnlocked_clippedQs() {
+ mScrimController.setClipsQsScrim(true);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mNotificationsScrim, false,
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
+ mScrimController.setRawPanelExpansionFraction(0.25f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
+ mScrimController.setRawPanelExpansionFraction(0.5f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ }
+
+ @Test
+ public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true);
+ mScrimController.setClipsQsScrim(false);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mNotificationsScrim, false,
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ // The large screens interpolator used in this test is a linear one, just for tests.
+ // Assertions below are based on this assumption, and that the code uses that interpolator
+ // when on a large screen (QS not clipped).
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(0.5f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(0.99f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(1f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ }
+
+
+ @Test
+ public void transitionToUnlocked_nonClippedQs_flagFalse() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false);
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -691,7 +778,6 @@
mScrimBehind, OPAQUE));
}
-
@Test
public void scrimStateCallback() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -879,17 +965,25 @@
// GIVEN display does NOT need blanking
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
- mScrimController = new ScrimController(mLightBarController,
- mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
- new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
- mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+ mScrimController = new ScrimController(
+ mLightBarController,
+ mDozeParameters,
+ mAlarmManager,
+ mKeyguardStateController,
+ mDelayedWakeLockBuilder,
+ new FakeHandler(mLooper.getLooper()),
+ mKeyguardUpdateMonitor,
+ mDockManager,
+ mConfigurationController,
+ new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
- mSysuiStatusBarStateController,
- mMainDispatcher);
+ mMainDispatcher,
+ mLinearLargeScreenShadeInterpolator,
+ mFeatureFlags);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
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 158e9ad..e2019b2 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
@@ -683,4 +683,30 @@
// the following call before registering centralSurfaces should NOT throw a NPE:
mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
}
+
+ @Test
+ public void testResetHideBouncerWhenShowing_alternateBouncerHides() {
+ // GIVEN the keyguard is showing
+ reset(mAlternateBouncerInteractor);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ // WHEN SBKV is reset with hideBouncerWhenShowing=true
+ mStatusBarKeyguardViewManager.reset(true);
+
+ // THEN alternate bouncer is hidden
+ verify(mAlternateBouncerInteractor).hide();
+ }
+
+ @Test
+ public void testResetHideBouncerWhenShowingIsFalse_alternateBouncerHides() {
+ // GIVEN the keyguard is showing
+ reset(mAlternateBouncerInteractor);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ // WHEN SBKV is reset with hideBouncerWhenShowing=false
+ mStatusBarKeyguardViewManager.reset(false);
+
+ // THEN alternate bouncer is NOT hidden
+ verify(mAlternateBouncerInteractor, never()).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ccc57ad..ee7e082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -18,6 +18,9 @@
import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.mockito.AdditionalAnswers.answerVoid;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,7 +31,6 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -38,6 +40,8 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -51,6 +55,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
@@ -90,9 +95,12 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -398,4 +406,40 @@
// THEN display should try wake up for the full screen intent
verify(mCentralSurfaces).wakeUpForFullScreenIntent();
}
+
+ @Test
+ public void testOnFullScreenIntentWhenDozing_logToStatsd() {
+ final int kTestUid = 12345;
+ final String kTestActivityName = "TestActivity";
+ // GIVEN entry that can has a full screen intent that can show
+ PendingIntent mockFullScreenIntent = mock(PendingIntent.class);
+ when(mockFullScreenIntent.getCreatorUid()).thenReturn(kTestUid);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.name = kTestActivityName;
+ when(mockFullScreenIntent.queryIntentComponents(anyInt()))
+ .thenReturn(Arrays.asList(resolveInfo));
+ Notification.Builder nb = new Notification.Builder(mContext, "a")
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFullScreenIntent(mockFullScreenIntent, true);
+ StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0,
+ "tag" + System.currentTimeMillis(), 0, 0,
+ nb.build(), new UserHandle(0), null, 0);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
+ when(entry.getSbn()).thenReturn(sbn);
+ MockitoSession mockingSession = mockitoSession()
+ .mockStatic(FrameworkStatsLog.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // WHEN
+ mNotificationActivityStarter.launchFullScreenIntent(entry);
+
+ // THEN the full screen intent should be logged to statsd.
+ verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.FULL_SCREEN_INTENT_LAUNCHED,
+ kTestUid, kTestActivityName));
+ mockingSession.finishMocking();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
index 35dea60..7c9351c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
@@ -47,14 +47,14 @@
val expectedNetId = NET_1_ID.toString()
val expectedCaps = NET_1_CAPS.toString()
- assertThat(actualString).contains("true")
+ assertThat(actualString).contains("onDefaultCapabilitiesChanged")
assertThat(actualString).contains(expectedNetId)
assertThat(actualString).contains(expectedCaps)
}
@Test
fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
- logger.logOnLost(NET_1)
+ logger.logOnLost(NET_1, isDefaultNetworkCallback = false)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -62,6 +62,7 @@
val expectedNetId = NET_1_ID.toString()
+ assertThat(actualString).contains("onLost")
assertThat(actualString).contains(expectedNetId)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
deleted file mode 100644
index 45189cf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.model
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_IN
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_OUT
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-@SmallTest
-class MobileConnectionModelTest : SysuiTestCase() {
-
- @Test
- fun `log diff - initial log contains all columns`() {
- val logger = TestLogger()
- val connection = MobileConnectionModel()
-
- connection.logFull(logger)
-
- assertThat(logger.changes)
- .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
- assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
- assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
- assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
- assertThat(logger.changes)
- .contains(
- Pair(
- COL_ACTIVITY_DIRECTION_IN,
- connection.dataActivityDirection.hasActivityIn.toString(),
- )
- )
- assertThat(logger.changes)
- .contains(
- Pair(
- COL_ACTIVITY_DIRECTION_OUT,
- connection.dataActivityDirection.hasActivityOut.toString(),
- )
- )
- assertThat(logger.changes)
- .contains(
- Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
- )
- assertThat(logger.changes)
- .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
- }
-
- @Test
- fun `log diff - primary level changes - only level is logged`() {
- val logger = TestLogger()
- val connectionOld = MobileConnectionModel(primaryLevel = 1)
-
- val connectionNew = MobileConnectionModel(primaryLevel = 2)
-
- connectionNew.logDiffs(connectionOld, logger)
-
- assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
- }
-
- private class TestLogger : TableRowLogger {
- val changes = mutableListOf<Pair<String, String>>()
-
- override fun logChange(columnName: String, value: String?) {
- changes.add(Pair(columnName, value.toString()))
- }
-
- override fun logChange(columnName: String, value: Int) {
- changes.add(Pair(columnName, value.toString()))
- }
-
- override fun logChange(columnName: String, value: Boolean) {
- changes.add(Pair(columnName, value.toString()))
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 53cd71f1..44fbd5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -27,8 +29,19 @@
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
- private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
- override val connectionInfo = _connectionInfo
+ override val isEmergencyOnly = MutableStateFlow(false)
+ override val isRoaming = MutableStateFlow(false)
+ override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val isInService = MutableStateFlow(false)
+ override val isGsm = MutableStateFlow(false)
+ override val cdmaLevel = MutableStateFlow(0)
+ override val primaryLevel = MutableStateFlow(0)
+ override val dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+ override val dataActivityDirection =
+ MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ override val carrierNetworkChangeActive = MutableStateFlow(false)
+ override val resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+ MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
@@ -40,10 +53,6 @@
override val networkName =
MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
- fun setConnectionInfo(model: MobileConnectionModel) {
- _connectionInfo.value = model
- }
-
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index b072dee..37fac34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -36,8 +35,11 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -123,34 +125,49 @@
assertConnection(underTest, networkModel)
}
- private fun assertConnection(
+ private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+ val job = launch {
+ launch { conn.cdmaLevel.collect {} }
+ launch { conn.primaryLevel.collect {} }
+ launch { conn.dataActivityDirection.collect {} }
+ launch { conn.carrierNetworkChangeActive.collect {} }
+ launch { conn.isRoaming.collect {} }
+ launch { conn.networkName.collect {} }
+ launch { conn.isEmergencyOnly.collect {} }
+ launch { conn.dataConnectionState.collect {} }
+ }
+ return job
+ }
+
+ private fun TestScope.assertConnection(
conn: DemoMobileConnectionRepository,
model: FakeNetworkEventModel
) {
+ val job = startCollection(underTest)
when (model) {
is FakeNetworkEventModel.Mobile -> {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.value)
.isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
- assertThat(connectionInfo.carrierNetworkChangeActive)
+ assertThat(conn.carrierNetworkChangeActive.value)
.isEqualTo(model.carrierNetworkChange)
- assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
assertThat(conn.networkName.value)
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387): check these once we start handling them
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState)
- .isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
}
// MobileDisabled isn't combinatorial in nature, and is tested in
// DemoMobileConnectionsRepositoryTest.kt
else -> {}
}
+
+ job.cancel()
}
/** Matches [FakeNetworkEventModel] */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index f60d92b..0e45d8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -26,7 +26,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -40,9 +39,11 @@
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -524,47 +525,65 @@
job.cancel()
}
- private fun assertConnection(
+ private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+ val job = launch {
+ launch { conn.cdmaLevel.collect {} }
+ launch { conn.primaryLevel.collect {} }
+ launch { conn.dataActivityDirection.collect {} }
+ launch { conn.carrierNetworkChangeActive.collect {} }
+ launch { conn.isRoaming.collect {} }
+ launch { conn.networkName.collect {} }
+ launch { conn.isEmergencyOnly.collect {} }
+ launch { conn.dataConnectionState.collect {} }
+ }
+ return job
+ }
+
+ private fun TestScope.assertConnection(
conn: DemoMobileConnectionRepository,
- model: FakeNetworkEventModel
+ model: FakeNetworkEventModel,
) {
+ val job = startCollection(conn)
+ // Assert the fields using the `MutableStateFlow` so that we don't have to start up
+ // a collector for every field for every test
when (model) {
is FakeNetworkEventModel.Mobile -> {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.value)
.isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
- assertThat(connectionInfo.carrierNetworkChangeActive)
+ assertThat(conn.carrierNetworkChangeActive.value)
.isEqualTo(model.carrierNetworkChange)
- assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
assertThat(conn.networkName.value)
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387) check these once we start handling them
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState)
- .isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
}
else -> {}
}
+
+ job.cancel()
}
- private fun assertCarrierMergedConnection(
+ private fun TestScope.assertCarrierMergedConnection(
conn: DemoMobileConnectionRepository,
model: FakeWifiEventModel.CarrierMerged,
) {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+ val job = startCollection(conn)
assertThat(conn.subId).isEqualTo(model.subscriptionId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false)
- assertThat(connectionInfo.isRoaming).isEqualTo(false)
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.carrierNetworkChangeActive.value).isEqualTo(false)
+ assertThat(conn.isRoaming.value).isEqualTo(false)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+ job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index f0f213b..441186a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -22,7 +22,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -75,36 +74,48 @@
}
@Test
- fun connectionInfo_inactiveWifi_isDefault() =
+ fun inactiveWifi_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestConnState: DataConnectionState? = null
+ var latestNetType: ResolvedNetworkType? = null
+
+ val dataJob =
+ underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+ val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ dataJob.cancel()
+ netJob.cancel()
}
@Test
- fun connectionInfo_activeWifi_isDefault() =
+ fun activeWifi_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestConnState: DataConnectionState? = null
+ var latestNetType: ResolvedNetworkType? = null
+
+ val dataJob =
+ underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+ val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1))
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ dataJob.cancel()
+ netJob.cancel()
}
@Test
- fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
+ fun carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
@@ -117,34 +128,16 @@
)
)
- val expected =
- MobileConnectionModel(
- primaryLevel = 3,
- cdmaLevel = 3,
- dataConnectionState = DataConnectionState.Connected,
- dataActivityDirection =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
- resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- isRoaming = false,
- isEmergencyOnly = false,
- operatorAlphaShort = null,
- isInService = true,
- isGsm = false,
- carrierNetworkChangeActive = false,
- )
- assertThat(latest).isEqualTo(expected)
+ assertThat(latest).isEqualTo(3)
job.cancel()
}
@Test
- fun connectionInfo_activity_comesFromWifiActivity() =
+ fun activity_comesFromWifiActivity() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataActivityModel? = null
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
@@ -162,8 +155,8 @@
)
)
- assertThat(latest!!.dataActivityDirection.hasActivityIn).isTrue()
- assertThat(latest!!.dataActivityDirection.hasActivityOut).isFalse()
+ assertThat(latest!!.hasActivityIn).isTrue()
+ assertThat(latest!!.hasActivityOut).isFalse()
wifiRepository.setWifiActivity(
DataActivityModel(
@@ -172,17 +165,19 @@
)
)
- assertThat(latest!!.dataActivityDirection.hasActivityIn).isFalse()
- assertThat(latest!!.dataActivityDirection.hasActivityOut).isTrue()
+ assertThat(latest!!.hasActivityIn).isFalse()
+ assertThat(latest!!.hasActivityOut).isTrue()
job.cancel()
}
@Test
- fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
+ fun carrierMergedWifi_wrongSubId_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestLevel: Int? = null
+ var latestType: ResolvedNetworkType? = null
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
+ val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -192,20 +187,19 @@
)
)
- assertThat(latest).isEqualTo(MobileConnectionModel())
- assertThat(latest!!.primaryLevel).isNotEqualTo(3)
- assertThat(latest!!.resolvedNetworkType)
- .isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ assertThat(latestLevel).isNotEqualTo(3)
+ assertThat(latestType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ levelJob.cancel()
+ typeJob.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
- fun connectionInfo_carrierMergedButNotEnabled_isDefault() =
+ fun carrierMergedButNotEnabled_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -216,17 +210,17 @@
)
wifiRepository.setIsWifiEnabled(false)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latest).isNotEqualTo(3)
job.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
- fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() =
+ fun carrierMergedButWifiNotDefault_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -237,7 +231,7 @@
)
wifiRepository.setIsWifiDefault(false)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latest).isNotEqualTo(3)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index cd4d847..db5a7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -24,13 +24,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -94,16 +93,16 @@
@Test
fun startingIsCarrierMerged_usesCarrierMergedInitially() =
testScope.runTest {
- val carrierMergedConnectionInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- )
- carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo)
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Non-carrier-merged"
+
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+ mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
initializeRepo(startingIsCarrierMerged = true)
assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo)
- assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo)
+ assertThat(underTest.operatorAlphaShort.value).isEqualTo(carrierMergedOperatorName)
verify(mobileFactory, never())
.build(
SUB_ID,
@@ -116,16 +115,16 @@
@Test
fun startingNotCarrierMerged_usesTypicalInitially() =
testScope.runTest {
- val mobileConnectionInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Operator",
- )
- mobileRepo.setConnectionInfo(mobileConnectionInfo)
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Typical Operator"
+
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+ mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
initializeRepo(startingIsCarrierMerged = false)
assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
- assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
+ assertThat(underTest.operatorAlphaShort.value).isEqualTo(nonCarrierMergedName)
verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer)
}
@@ -156,39 +155,40 @@
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
+
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
underTest.setIsCarrierMerged(true)
- val info1 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- primaryLevel = 1,
- )
- carrierMergedRepo.setConnectionInfo(info1)
+ val operator1 = "Carrier Merged Operator"
+ val level1 = 1
+ carrierMergedRepo.operatorAlphaShort.value = operator1
+ carrierMergedRepo.primaryLevel.value = level1
- assertThat(latest).isEqualTo(info1)
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
- val info2 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator #2",
- primaryLevel = 2,
- )
- carrierMergedRepo.setConnectionInfo(info2)
+ val operator2 = "Carrier Merged Operator #2"
+ val level2 = 2
+ carrierMergedRepo.operatorAlphaShort.value = operator2
+ carrierMergedRepo.primaryLevel.value = level2
- assertThat(latest).isEqualTo(info2)
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
- val info3 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator #3",
- primaryLevel = 3,
- )
- carrierMergedRepo.setConnectionInfo(info3)
+ val operator3 = "Carrier Merged Operator #3"
+ val level3 = 3
+ carrierMergedRepo.operatorAlphaShort.value = operator3
+ carrierMergedRepo.primaryLevel.value = level3
- assertThat(latest).isEqualTo(info3)
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -196,39 +196,40 @@
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
+
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
underTest.setIsCarrierMerged(false)
- val info1 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator",
- primaryLevel = 1,
- )
- mobileRepo.setConnectionInfo(info1)
+ val operator1 = "Typical Merged Operator"
+ val level1 = 1
+ mobileRepo.operatorAlphaShort.value = operator1
+ mobileRepo.primaryLevel.value = level1
- assertThat(latest).isEqualTo(info1)
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
- val info2 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator #2",
- primaryLevel = 2,
- )
- mobileRepo.setConnectionInfo(info2)
+ val operator2 = "Typical Merged Operator #2"
+ val level2 = 2
+ mobileRepo.operatorAlphaShort.value = operator2
+ mobileRepo.primaryLevel.value = level2
- assertThat(latest).isEqualTo(info2)
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
- val info3 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator #3",
- primaryLevel = 3,
- )
- mobileRepo.setConnectionInfo(info3)
+ val operator3 = "Typical Merged Operator #3"
+ val level3 = 3
+ mobileRepo.operatorAlphaShort.value = operator3
+ mobileRepo.primaryLevel.value = level3
- assertThat(latest).isEqualTo(info3)
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -236,57 +237,58 @@
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
- val carrierMergedInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- primaryLevel = 4,
- )
- carrierMergedRepo.setConnectionInfo(carrierMergedInfo)
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
- val mobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Operator",
- primaryLevel = 2,
- )
- mobileRepo.setConnectionInfo(mobileInfo)
+ val carrierMergedOperator = "Carrier Merged Operator"
+ val carrierMergedLevel = 4
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperator
+ carrierMergedRepo.primaryLevel.value = carrierMergedLevel
+
+ val mobileName = "Typical Operator"
+ val mobileLevel = 2
+ mobileRepo.operatorAlphaShort.value = mobileName
+ mobileRepo.primaryLevel.value = mobileLevel
// Start with the mobile info
- assertThat(latest).isEqualTo(mobileInfo)
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
// WHEN isCarrierMerged is set to true
underTest.setIsCarrierMerged(true)
// THEN the carrier merged info is used
- assertThat(latest).isEqualTo(carrierMergedInfo)
+ assertThat(latestName).isEqualTo(carrierMergedOperator)
+ assertThat(latestLevel).isEqualTo(carrierMergedLevel)
- val newCarrierMergedInfo =
- MobileConnectionModel(
- operatorAlphaShort = "New CM Operator",
- primaryLevel = 0,
- )
- carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo)
+ val newCarrierMergedName = "New CM Operator"
+ val newCarrierMergedLevel = 0
+ carrierMergedRepo.operatorAlphaShort.value = newCarrierMergedName
+ carrierMergedRepo.primaryLevel.value = newCarrierMergedLevel
- assertThat(latest).isEqualTo(newCarrierMergedInfo)
+ assertThat(latestName).isEqualTo(newCarrierMergedName)
+ assertThat(latestLevel).isEqualTo(newCarrierMergedLevel)
// WHEN isCarrierMerged is set to false
underTest.setIsCarrierMerged(false)
// THEN the typical info is used
- assertThat(latest).isEqualTo(mobileInfo)
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
- val newMobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "New Mobile Operator",
- primaryLevel = 3,
- )
- mobileRepo.setConnectionInfo(newMobileInfo)
+ val newMobileName = "New MobileOperator"
+ val newMobileLevel = 3
+ mobileRepo.operatorAlphaShort.value = newMobileName
+ mobileRepo.primaryLevel.value = newMobileLevel
- assertThat(latest).isEqualTo(newMobileInfo)
+ assertThat(latestName).isEqualTo(newMobileName)
+ assertThat(latestLevel).isEqualTo(newMobileLevel)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -370,7 +372,8 @@
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val emergencyJob = underTest.isEmergencyOnly.launchIn(this)
+ val operatorJob = underTest.operatorAlphaShort.launchIn(this)
// WHEN we set up some mobile connection info
val serviceState = ServiceState()
@@ -394,7 +397,8 @@
assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
- job.cancel()
+ emergencyJob.cancel()
+ operatorJob.cancel()
}
@Test
@@ -409,7 +413,7 @@
initializeRepo(startingIsCarrierMerged = true)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
// WHEN we set up carrier merged info
val networkId = 2
@@ -452,7 +456,7 @@
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
// WHEN we set up some mobile connection info
val signalStrength = mock<SignalStrength>()
@@ -502,12 +506,7 @@
assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
// WHEN the normal network is updated
- val newMobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Mobile Operator 2",
- primaryLevel = 0,
- )
- mobileRepo.setConnectionInfo(newMobileInfo)
+ mobileRepo.primaryLevel.value = 0
// THEN the new level is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
@@ -529,7 +528,7 @@
// WHEN isCarrierMerged = false
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.level).thenReturn(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index bd5a4d7..f6e5959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -50,13 +50,15 @@
import android.telephony.TelephonyManager.EXTRA_SPN
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
@@ -135,235 +137,285 @@
}
@Test
- fun testFlowForSubId_default() =
+ fun emergencyOnly() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(MobileConnectionModel())
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly() =
- runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
- assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+ assertThat(latest).isEqualTo(true)
job.cancel()
}
@Test
- fun testFlowForSubId_emergencyOnly_toggles() =
+ fun emergencyOnly_toggles() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
callback.onServiceStateChanged(serviceState)
+ assertThat(latest).isTrue()
+
serviceState.isEmergencyOnly = false
callback.onServiceStateChanged(serviceState)
- assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+ assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ fun cdmaLevelUpdates() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
- val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
callback.onSignalStrengthsChanged(strength)
- assertThat(latest?.isGsm).isEqualTo(true)
- assertThat(latest?.primaryLevel).isEqualTo(1)
- assertThat(latest?.cdmaLevel).isEqualTo(2)
+ assertThat(latest).isEqualTo(2)
+
+ // gsmLevel updates, no change to cdmaLevel
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+
+ assertThat(latest).isEqualTo(2)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_connected() =
+ fun gsmLevelUpdates() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(1)
+
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun isGsm() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isTrue()
+
+ strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = false)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataConnectionState_connected() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+ assertThat(latest).isEqualTo(DataConnectionState.Connected)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_connecting() =
+ fun dataConnectionState_connecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
+ assertThat(latest).isEqualTo(DataConnectionState.Connecting)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_disconnected() =
+ fun dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnected)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_disconnecting() =
+ fun dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnecting)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_suspended() =
+ fun dataConnectionState_suspended() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+ assertThat(latest).isEqualTo(DataConnectionState.Suspended)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+ fun dataConnectionState_handoverInProgress() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
- assertThat(latest?.dataConnectionState)
- .isEqualTo(DataConnectionState.HandoverInProgress)
+ assertThat(latest).isEqualTo(DataConnectionState.HandoverInProgress)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_unknown() =
+ fun dataConnectionState_unknown() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+ assertThat(latest).isEqualTo(DataConnectionState.Unknown)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_invalid() =
+ fun dataConnectionState_invalid() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(45, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+ assertThat(latest).isEqualTo(DataConnectionState.Invalid)
job.cancel()
}
@Test
- fun testFlowForSubId_dataActivity() =
+ fun dataActivity() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataActivityModel? = null
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DataActivityListener>()
callback.onDataActivity(DATA_ACTIVITY_INOUT)
- assertThat(latest?.dataActivityDirection)
- .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
+ assertThat(latest).isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
job.cancel()
}
@Test
- fun testFlowForSubId_carrierNetworkChange() =
+ fun carrierNetworkChange() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
- assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+ assertThat(latest).isEqualTo(true)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_default() =
+ fun networkType_default() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val expected = UnknownNetworkType
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_updatesUsingDefault() =
+ fun networkType_unknown_hasCorrectKey() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = NETWORK_TYPE_UNKNOWN
+ val expected = UnknownNetworkType
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest).isEqualTo(expected)
+ assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_updatesUsingDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
@@ -371,16 +423,16 @@
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_updatesUsingOverride() =
+ fun networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
@@ -392,7 +444,7 @@
}
callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -466,7 +518,7 @@
fun `roaming - gsm - queries service state`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.roaming = false
@@ -492,8 +544,7 @@
fun `activity - updates from callback`() =
runBlocking(IMMEDIATE) {
var latest: DataActivityModel? = null
- val job =
- underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this)
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
@@ -611,8 +662,7 @@
runBlocking(IMMEDIATE) {
var latest: String? = null
- val job =
- underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this)
+ val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
val shortName = "short name"
val serviceState = ServiceState()
@@ -633,7 +683,7 @@
fun `connection model - isInService - not iwlan`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.voiceRegState = STATE_IN_SERVICE
@@ -658,7 +708,7 @@
fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
// Mock the service state here so we can make it specifically IWLAN
val serviceState: ServiceState = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index fa072fc..1eb1056 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,7 +23,6 @@
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -74,9 +73,7 @@
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(isGsm = true),
- )
+ connectionRepository.isGsm.value = true
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -89,13 +86,9 @@
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL
- ),
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -108,13 +101,9 @@
@Test
fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL,
- ),
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
@@ -128,9 +117,7 @@
@Test
fun notGsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(isGsm = false),
- )
+ connectionRepository.isGsm.value = false
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -142,13 +129,9 @@
@Test
fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL
- ),
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
@@ -162,13 +145,9 @@
@Test
fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL,
- ),
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = false
var latest: Int? = null
@@ -197,11 +176,8 @@
@Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -214,23 +190,14 @@
@Test
fun iconGroup_updates_on_change() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(FOUR_G),
- ),
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
yield()
assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
@@ -241,12 +208,8 @@
@Test
fun iconGroup_5g_override_type() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -259,12 +222,8 @@
@Test
fun iconGroup_default_if_no_lookup() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)),
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -277,11 +236,7 @@
@Test
fun iconGroup_carrierMerged_usesOverride() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = CarrierMergedNetworkType,
- ),
- )
+ connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -295,11 +250,8 @@
fun `icon group - checks default data`() =
runBlocking(IMMEDIATE) {
mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -380,9 +332,7 @@
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
- )
+ connectionRepository.dataConnectionState.value = DataConnectionState.Connected
yield()
assertThat(latest).isTrue()
@@ -396,9 +346,7 @@
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
- )
+ connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
assertThat(latest).isFalse()
@@ -411,11 +359,11 @@
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = true))
+ connectionRepository.isInService.value = true
assertThat(latest).isTrue()
- connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = false))
+ connectionRepository.isInService.value = false
assertThat(latest).isFalse()
@@ -429,22 +377,13 @@
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = false,
- )
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.isRoaming.value = false
yield()
assertThat(latest).isFalse()
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = true,
- )
- )
+ connectionRepository.isRoaming.value = true
yield()
assertThat(latest).isTrue()
@@ -459,23 +398,15 @@
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = false
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = true,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = false,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = false
yield()
assertThat(latest).isTrue()
@@ -490,25 +421,15 @@
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = true,
- carrierNetworkChangeActive = true,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = true,
- carrierNetworkChangeActive = true,
- )
- )
+ connectionRepository.isGsm.value = true
yield()
assertThat(latest).isFalse()
@@ -526,24 +447,20 @@
// Default network name, operator name is non-null, uses the operator name
connectionRepository.networkName.value = DEFAULT_NAME
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(operatorAlphaShort = testOperatorName)
- )
+ connectionRepository.operatorAlphaShort.value = testOperatorName
yield()
assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
// Default network name, operator name is null, uses the default
- connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
+ connectionRepository.operatorAlphaShort.value = null
yield()
assertThat(latest).isEqualTo(DEFAULT_NAME)
// Derived network name, operator name non-null, uses the derived name
connectionRepository.networkName.value = DERIVED_NAME
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(operatorAlphaShort = testOperatorName)
- )
+ connectionRepository.operatorAlphaShort.value = testOperatorName
yield()
assertThat(latest).isEqualTo(DERIVED_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index d00acb8..3ed6cc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -29,6 +29,8 @@
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
@@ -62,6 +64,7 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertNotNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -72,6 +75,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -96,6 +100,7 @@
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: UserInteractor
@@ -154,6 +159,7 @@
repository = telephonyRepository,
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
@@ -177,6 +183,18 @@
}
@Test
+ fun `testKeyguardUpdateMonitor_onKeyguardGoingAway`() =
+ testScope.runTest {
+ val argumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ verify(keyguardUpdateMonitor).registerCallback(argumentCaptor.capture())
+
+ argumentCaptor.value.onKeyguardGoingAway()
+
+ val lastValue = collectLastValue(underTest.dialogDismissRequests)
+ assertNotNull(lastValue)
+ }
+
+ @Test
fun `onRecordSelected - user`() =
testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 22fc32a..daa71b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -25,6 +25,7 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -80,6 +81,7 @@
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: StatusBarUserChipViewModel
@@ -263,6 +265,7 @@
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index a2bd8d3..e08ebf4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,7 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -81,6 +82,7 @@
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: UserSwitcherViewModel
@@ -165,6 +167,7 @@
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 5ecc7f2..c869fcd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -873,10 +873,6 @@
mAutomaticBrightnessController.stop();
}
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.stop();
- }
-
if (mBrightnessSetting != null) {
mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
}
@@ -1125,6 +1121,7 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
+ mScreenOffBrightnessSensorController = null;
}
loadScreenOffBrightnessSensor();
int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
@@ -1242,6 +1239,10 @@
mPowerState.stop();
mPowerState = null;
}
+
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.stop();
+ }
}
private void updatePowerState() {
diff --git a/services/core/java/com/android/server/media/projection/OWNERS b/services/core/java/com/android/server/media/projection/OWNERS
index 9ca3910..832bcd9 100644
--- a/services/core/java/com/android/server/media/projection/OWNERS
+++ b/services/core/java/com/android/server/media/projection/OWNERS
@@ -1,2 +1 @@
-michaelwr@google.com
-santoscordon@google.com
+include /media/java/android/media/projection/OWNERS
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f913cef..bb6db9a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -199,6 +199,7 @@
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.ExtconStateObserver;
import com.android.server.ExtconUEventObserver;
import com.android.server.GestureLauncherService;
@@ -407,6 +408,7 @@
AppOpsManager mAppOpsManager;
PackageManager mPackageManager;
SideFpsEventHandler mSideFpsEventHandler;
+ LockPatternUtils mLockPatternUtils;
private boolean mHasFeatureAuto;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -1056,8 +1058,10 @@
}
synchronized (mLock) {
- // Lock the device after the dream transition has finished.
- mLockAfterAppTransitionFinished = true;
+ // If the setting to lock instantly on power button press is true, then set the flag to
+ // lock after the dream transition has finished.
+ mLockAfterAppTransitionFinished =
+ mLockPatternUtils.getPowerButtonInstantlyLocks(mCurrentUserId);
}
dreamManagerInternal.requestDream();
@@ -1929,6 +1933,7 @@
mHasFeatureHdmiCec = mPackageManager.hasSystemFeature(FEATURE_HDMI_CEC);
mAccessibilityShortcutController =
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
+ mLockPatternUtils = new LockPatternUtils(mContext);
mLogger = new MetricsLogger();
mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b4b8cf9..43fb44c 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -169,6 +169,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.app.procstats.StatsEventOutput;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelAllocationStats;
@@ -612,12 +613,19 @@
}
case FrameworkStatsLog.PROC_STATS:
synchronized (mProcStatsLock) {
- return pullProcStatsLocked(ProcessStats.REPORT_ALL, atomTag, data);
+ return pullProcStatsLocked(atomTag, data);
}
case FrameworkStatsLog.PROC_STATS_PKG_PROC:
synchronized (mProcStatsLock) {
- return pullProcStatsLocked(ProcessStats.REPORT_PKG_PROC_STATS, atomTag,
- data);
+ return pullProcStatsLocked(atomTag, data);
+ }
+ case FrameworkStatsLog.PROCESS_STATE:
+ synchronized (mProcStatsLock) {
+ return pullProcessStateLocked(atomTag, data);
+ }
+ case FrameworkStatsLog.PROCESS_ASSOCIATION:
+ synchronized (mProcStatsLock) {
+ return pullProcessAssociationLocked(atomTag, data);
}
case FrameworkStatsLog.DISK_IO:
synchronized (mDiskIoLock) {
@@ -890,6 +898,8 @@
registerNumFacesEnrolled();
registerProcStats();
registerProcStatsPkgProc();
+ registerProcessState();
+ registerProcessAssociation();
registerDiskIO();
registerPowerProfile();
registerProcessCpuTime();
@@ -2870,59 +2880,138 @@
);
}
- private int pullProcStatsLocked(int section, int atomTag, List<StatsEvent> pulledData) {
+ private void registerProcessState() {
+ int tagId = FrameworkStatsLog.PROCESS_STATE;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl);
+ }
+
+ private void registerProcessAssociation() {
+ int tagId = FrameworkStatsLog.PROCESS_ASSOCIATION;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl);
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private ProcessStats getStatsFromProcessStatsService(int atomTag) {
IProcessStats processStatsService = getIProcessStatsService();
if (processStatsService == null) {
- return StatsManager.PULL_SKIP;
+ return null;
}
-
final long token = Binder.clearCallingIdentity();
try {
// force procstats to flush & combine old files into one store
- long lastHighWaterMark = readProcStatsHighWaterMark(section);
-
- ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
- for (int i = 0; i < protoStreams.length; i++) {
- protoStreams[i] = new ProtoOutputStream();
- }
-
+ long lastHighWaterMark = readProcStatsHighWaterMark(atomTag);
ProcessStats procStats = new ProcessStats(false);
// Force processStatsService to aggregate all in-storage and in-memory data.
- long highWaterMark = processStatsService.getCommittedStatsMerged(
- lastHighWaterMark, section, true, null, procStats);
- procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
-
- for (int i = 0; i < protoStreams.length; i++) {
- byte[] bytes = protoStreams[i].getBytes(); // cache the value
- if (bytes.length > 0) {
- pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, bytes,
- // This is a shard ID, and is specified in the metric definition to be
- // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE to
- // keep all the shards, as it thinks each shard is a different dimension
- // of data.
- i));
- }
- }
-
- new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark)
+ long highWaterMark =
+ processStatsService.getCommittedStatsMerged(
+ lastHighWaterMark,
+ ProcessStats.REPORT_ALL, // ignored since committedStats below is null.
+ true,
+ null, // committedStats
+ procStats);
+ new File(
+ mBaseDir.getAbsolutePath()
+ + "/"
+ + highWaterMarkFilePrefix(atomTag)
+ + "_"
+ + lastHighWaterMark)
.delete();
- new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + highWaterMark)
+ new File(
+ mBaseDir.getAbsolutePath()
+ + "/"
+ + highWaterMarkFilePrefix(atomTag)
+ + "_"
+ + highWaterMark)
.createNewFile();
+ return procStats;
} catch (RemoteException | IOException e) {
Slog.e(TAG, "Getting procstats failed: ", e);
- return StatsManager.PULL_SKIP;
+ return null;
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
+ for (int i = 0; i < protoStreams.length; i++) {
+ protoStreams[i] = new ProtoOutputStream();
+ }
+ procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
+ for (int i = 0; i < protoStreams.length; i++) {
+ byte[] bytes = protoStreams[i].getBytes(); // cache the value
+ if (bytes.length > 0) {
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ bytes,
+ // This is a shard ID, and is specified in the metric definition to
+ // be
+ // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE
+ // to
+ // keep all the shards, as it thinks each shard is a different
+ // dimension
+ // of data.
+ i));
+ }
+ }
return StatsManager.PULL_SUCCESS;
}
+ @GuardedBy("mProcStatsLock")
+ private int pullProcessStateLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ procStats.dumpProcessState(atomTag, new StatsEventOutput(pulledData));
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcessAssociationLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ procStats.dumpProcessAssociation(atomTag, new StatsEventOutput(pulledData));
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private String highWaterMarkFilePrefix(int atomTag) {
+ // For backward compatibility, use the legacy ProcessStats enum value as the prefix for
+ // PROC_STATS and PROC_STATS_PKG_PROC.
+ if (atomTag == FrameworkStatsLog.PROC_STATS) {
+ return String.valueOf(ProcessStats.REPORT_ALL);
+ }
+ if (atomTag == FrameworkStatsLog.PROC_STATS_PKG_PROC) {
+ return String.valueOf(ProcessStats.REPORT_PKG_PROC_STATS);
+ }
+ return "atom-" + atomTag;
+ }
+
// read high watermark for section
- private long readProcStatsHighWaterMark(int section) {
+ private long readProcStatsHighWaterMark(int atomTag) {
try {
- File[] files = mBaseDir.listFiles((d, name) -> {
- return name.toLowerCase().startsWith(String.valueOf(section) + '_');
- });
+ File[] files =
+ mBaseDir.listFiles(
+ (d, name) -> {
+ return name.toLowerCase()
+ .startsWith(highWaterMarkFilePrefix(atomTag) + '_');
+ });
if (files == null || files.length == 0) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7489f80..7c9244e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -696,6 +696,8 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
+ EventLogTags.writeWmSetRequestedOrientation(requestedOrientation,
+ r.shortComponentName);
r.setRequestedOrientation(requestedOrientation);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c8f9db7..de5defa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1487,6 +1487,12 @@
mLastReportedMultiWindowMode = inPictureInPictureMode;
ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
true /* ignoreVisibility */);
+ if (inPictureInPictureMode && findMainWindow() == null) {
+ // Prevent malicious app entering PiP without valid WindowState, which can in turn
+ // result a non-touchable PiP window since the InputConsumer for PiP requires it.
+ EventLog.writeEvent(0x534e4554, "265293293", -1, "");
+ removeImmediately();
+ }
}
}
@@ -8008,9 +8014,7 @@
// The smallest screen width is the short side of screen bounds. Because the bounds
// and density won't be changed, smallestScreenWidthDp is also fixed.
overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
- // TODO(b/264276741): Check whether the runtime orietnation request is fixed rather than
- // the manifest orientation which may be obsolete.
- if (info.isFixedOrientation()) {
+ if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) {
// lock rotation too. When in size-compat, onConfigurationChanged will watch for and
// apply runtime rotation changes.
overrideConfig.windowConfiguration.setRotation(
@@ -8101,9 +8105,9 @@
if (isFixedOrientationLetterboxAllowed) {
resolveFixedOrientationConfiguration(newParentConfiguration);
}
-
- if (getCompatDisplayInsets() != null) {
- resolveSizeCompatModeConfiguration(newParentConfiguration);
+ final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
+ if (compatDisplayInsets != null) {
+ resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
} else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
// We ignore activities' requested orientation in multi-window modes. They may be
// taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8120,7 +8124,7 @@
resolveAspectRatioRestriction(newParentConfiguration);
}
- if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null
+ if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
updateResolvedBoundsPosition(newParentConfiguration);
@@ -8128,7 +8132,7 @@
boolean isIgnoreOrientationRequest = mDisplayContent != null
&& mDisplayContent.getIgnoreOrientationRequest();
- if (getCompatDisplayInsets() == null
+ if (compatDisplayInsets == null
// for size compat mode set in updateCompatDisplayInsets
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
@@ -8175,7 +8179,7 @@
info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
- getCompatDisplayInsets() != null,
+ compatDisplayInsets != null,
shouldCreateCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8187,7 +8191,7 @@
/**
* @return The orientation to use to understand if reachability is enabled.
*/
- @ActivityInfo.ScreenOrientation
+ @Configuration.Orientation
int getOrientationForReachability() {
return mLetterboxUiController.hasInheritedLetterboxBehavior()
? mLetterboxUiController.getInheritedOrientation()
@@ -8583,7 +8587,8 @@
* Resolves consistent screen configuration for orientation and rotation changes without
* inheriting the parent bounds.
*/
- private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) {
+ private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
+ @NonNull CompatDisplayInsets compatDisplayInsets) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
@@ -8604,13 +8609,13 @@
? requestedOrientation
// We should use the original orientation of the activity when possible to avoid
// forcing the activity in the opposite orientation.
- : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
- ? getCompatDisplayInsets().mOriginalRequestedOrientation
+ : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+ ? compatDisplayInsets.mOriginalRequestedOrientation
: newParentConfiguration.orientation;
int rotation = newParentConfiguration.windowConfiguration.getRotation();
final boolean isFixedToUserRotation = mDisplayContent == null
|| mDisplayContent.getDisplayRotation().isFixedToUserRotation();
- if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) {
+ if (!isFixedToUserRotation && !compatDisplayInsets.mIsFloating) {
// Use parent rotation because the original display can be rotated.
resolvedConfig.windowConfiguration.setRotation(rotation);
} else {
@@ -8626,11 +8631,11 @@
// rely on them to contain the original and unchanging width and height of the app.
final Rect containingAppBounds = new Rect();
final Rect containingBounds = mTmpBounds;
- getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation,
+ compatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
orientation, orientationRequested, isFixedToUserRotation);
resolvedBounds.set(containingBounds);
// The size of floating task is fixed (only swap), so the aspect ratio is already correct.
- if (!getCompatDisplayInsets().mIsFloating) {
+ if (!compatDisplayInsets.mIsFloating) {
mIsAspectRatioApplied =
applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
}
@@ -8639,7 +8644,7 @@
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getCompatDisplayInsets());
+ compatDisplayInsets);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 491e58b..c527310 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1486,7 +1486,7 @@
a.persistableMode = ActivityInfo.PERSIST_NEVER;
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
- a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_NO_HISTORY;
+ a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
a.configChanges = 0xffffffff;
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 1e5a219..48ce22e 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.server.wm
@@ -62,6 +62,10 @@
31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
31003 wm_task_removed (TaskId|1|5),(Reason|3)
+
+# Set the requested orientation of an activity.
+31006 wm_set_requested_orientation (Orientation|1|5),(Component Name|3)
+
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c20a513..d9f2b6e 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1433,7 +1433,7 @@
* the first opaque activity beneath.
*/
boolean hasInheritedLetterboxBehavior() {
- return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+ return mLetterboxConfigListener != null;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ce03244..9e0e8c4 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1436,7 +1436,7 @@
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
- @ScreenOrientation
+ @Configuration.Orientation
int getRequestedConfigurationOrientation() {
return getRequestedConfigurationOrientation(false /* forDisplay */);
}
@@ -1454,7 +1454,7 @@
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
- @ScreenOrientation
+ @Configuration.Orientation
int getRequestedConfigurationOrientation(boolean forDisplay) {
int requestedOrientation = getOverrideOrientation();
final RootDisplayArea root = getRootDisplayArea();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8931d80..e6c1e75 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6198,9 +6198,10 @@
waitingForConfig = waitingForRemoteDisplayChange = false;
numOpeningApps = 0;
}
- if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
- || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- || mClientFreezingScreen || numOpeningApps > 0) {
+ final boolean waitingForApps = mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT
+ && (mAppsFreezingScreen > 0 || numOpeningApps > 0);
+ if (waitingForConfig || waitingForRemoteDisplayChange || waitingForApps
+ || mClientFreezingScreen) {
ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
+ "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
+ "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 0993295..2505abf2 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -152,6 +152,8 @@
}
} catch (SQLiteException exception) {
Slog.w("SQLite exception when querying contacts.", exception);
+ } catch (IllegalArgumentException exception) {
+ Slog.w("Illegal Argument exception when querying contacts.", exception);
}
if (found && lookupKey != null && hasPhoneNumber) {
return queryPhoneNumber(lookupKey);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index eff9e8d..872734f 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -129,7 +129,6 @@
private final List<PeopleService.ConversationsListener> mConversationsListeners =
new ArrayList<>(1);
private final Handler mHandler;
-
private ContentObserver mCallLogContentObserver;
private ContentObserver mMmsSmsContentObserver;
@@ -1106,6 +1105,7 @@
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
mInjector.getBackgroundExecutor().execute(() -> {
PackageData packageData = getPackage(packageName, user.getIdentifier());
+ boolean hasCachedShortcut = false;
for (ShortcutInfo shortcut : shortcuts) {
if (ShortcutHelper.isConversationShortcut(
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
@@ -1114,15 +1114,18 @@
? packageData.getConversationInfo(shortcut.getId()) : null;
if (conversationInfo == null
|| !conversationInfo.isShortcutCachedForNotification()) {
- // This is a newly cached shortcut. Clean up the existing cached
- // shortcuts to ensure the cache size is under the limit.
- cleanupCachedShortcuts(user.getIdentifier(),
- MAX_CACHED_RECENT_SHORTCUTS - 1);
+ hasCachedShortcut = true;
}
}
addOrUpdateConversationInfo(shortcut);
}
}
+ // Added at least one new conversation. Uncache older existing cached
+ // shortcuts to ensure the cache size is under the limit.
+ if (hasCachedShortcut) {
+ cleanupCachedShortcuts(user.getIdentifier(),
+ MAX_CACHED_RECENT_SHORTCUTS);
+ }
});
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
new file mode 100644
index 0000000..832bcd9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -0,0 +1 @@
+include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 299f153..16a02b6 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -91,8 +91,16 @@
}
@Test
- public void testQueryException_returnsFalse() {
- contentProvider.setThrowException(true);
+ public void testQuerySQLiteException_returnsFalse() {
+ contentProvider.setThrowSQLiteException(true);
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+ assertFalse(mHelper.query(contactUri.toString()));
+ }
+
+ @Test
+ public void testQueryIllegalArgumentException_returnsFalse() {
+ contentProvider.setThrowIllegalArgumentException(true);
Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
assertFalse(mHelper.query(contactUri.toString()));
@@ -178,14 +186,18 @@
private class ContactsContentProvider extends MockContentProvider {
private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
- private boolean throwException = false;
+ private boolean mThrowSQLiteException = false;
+ private boolean mThrowIllegalArgumentException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- if (throwException) {
+ if (mThrowSQLiteException) {
throw new SQLiteException();
}
+ if (mThrowIllegalArgumentException) {
+ throw new IllegalArgumentException();
+ }
for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
if (uri.isPathPrefixMatch(prefixUri)) {
@@ -195,8 +207,12 @@
return mUriPrefixToCursorMap.get(uri);
}
- public void setThrowException(boolean throwException) {
- this.throwException = throwException;
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
+
+ public void setThrowIllegalArgumentException(boolean throwException) {
+ this.mThrowIllegalArgumentException = throwException;
}
private void registerCursor(Uri uriPrefix, Cursor cursor) {