Merge "Log.wtf when float properties in ViewState are set to NaN." into tm-qpr-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7c1f9c8..855366a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8045,8 +8045,9 @@
resultData.prepareToLeaveProcess(this);
}
upIntent.prepareToLeaveProcess(this);
- return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resultCode,
- resultData);
+ String resolvedType = upIntent.resolveTypeIfNeeded(getContentResolver());
+ return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resolvedType,
+ resultCode, resultData);
} else {
return mParent.navigateUpToFromChild(this, upIntent);
}
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 482f456..d1e6780 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -141,11 +141,11 @@
}
}
- boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+ boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType, int resultCode,
Intent resultData) {
try {
- return getActivityClientController().navigateUpTo(token, destIntent, resultCode,
- resultData);
+ return getActivityClientController().navigateUpTo(token, destIntent, resolvedType,
+ resultCode, resultData);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index f5e5cda..9aa67bc 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -60,8 +60,8 @@
in SizeConfigurationBuckets sizeConfigurations);
boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot);
boolean shouldUpRecreateTask(in IBinder token, in String destAffinity);
- boolean navigateUpTo(in IBinder token, in Intent target, int resultCode,
- in Intent resultData);
+ boolean navigateUpTo(in IBinder token, in Intent target, in String resolvedType,
+ int resultCode, in Intent resultData);
boolean releaseActivityInstance(in IBinder token);
boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask);
boolean finishActivityAffinity(in IBinder token);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 34c91c3..8e09939 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12150,6 +12150,15 @@
* Attempts by the admin to grant these permissions, when the admin is restricted from doing
* so, will be silently ignored (no exception will be thrown).
*
+ * Control over the following permissions are restricted for managed profile owners:
+ * <ul>
+ * <li>Manifest.permission.READ_SMS</li>
+ * </ul>
+ * <p>
+ * A managed profile owner may not grant these permissions (i.e. call this method with any of
+ * the permissions listed above and {@code grantState} of
+ * {@code #PERMISSION_GRANT_STATE_GRANTED}), but may deny them.
+ *
* @param admin Which profile or device owner this request is associated with.
* @param packageName The application to grant or revoke a permission to.
* @param permission The permission to grant or revoke.
diff --git a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
new file mode 100644
index 0000000..dc5a966
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+/** @hide */
+oneway interface IInputDeviceBatteryListener {
+
+ /**
+ * Called when there is a change in battery state for a monitored device. This will be called
+ * immediately after the listener is successfully registered for a new device via IInputManager.
+ * The parameters are values exposed through {@link android.hardware.BatteryState}.
+ */
+ void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status, float capacity,
+ long eventTime);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2da12e6c..a645ae4 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -20,6 +20,7 @@
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
@@ -155,4 +156,8 @@
void closeLightSession(int deviceId, in IBinder token);
void cancelCurrentTouch();
+
+ void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+ void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d17a952..97812ce 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -30,6 +30,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.BatteryState;
import android.hardware.SensorManager;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
@@ -66,6 +67,7 @@
import android.view.VerifiedInputEvent;
import android.view.WindowManager.LayoutParams;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -74,6 +76,8 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Provides information about input devices and available key layouts.
@@ -111,6 +115,14 @@
private TabletModeChangedListener mTabletModeChangedListener;
private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
+ private final Object mBatteryListenersLock = new Object();
+ // Maps a deviceId whose battery is currently being monitored to an entry containing the
+ // registered listeners for that device.
+ @GuardedBy("mBatteryListenersLock")
+ private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
+ @GuardedBy("mBatteryListenersLock")
+ private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+
private InputDeviceSensorManager mInputDeviceSensorManager;
/**
* Broadcast Action: Query available keyboard layouts.
@@ -1752,6 +1764,129 @@
}
/**
+ * Adds a battery listener to be notified about {@link BatteryState} changes for an input
+ * device. The same listener can be registered for multiple input devices.
+ * The listener will be notified of the initial battery state of the device after it is
+ * successfully registered.
+ * @param deviceId the input device that should be monitored
+ * @param executor an executor on which the callback will be called
+ * @param listener the {@link InputDeviceBatteryListener}
+ * @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+ * @hide
+ */
+ public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
+ @NonNull InputDeviceBatteryListener listener) {
+ Objects.requireNonNull(executor, "executor should not be null");
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) {
+ mBatteryListeners = new SparseArray<>();
+ mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
+ }
+ RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+ if (listenersForDevice == null) {
+ // The deviceId is currently not being monitored for battery changes.
+ // Start monitoring the device.
+ listenersForDevice = new RegisteredBatteryListeners();
+ mBatteryListeners.put(deviceId, listenersForDevice);
+ try {
+ mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ // The deviceId is already being monitored for battery changes.
+ // Ensure that the listener is not already registered.
+ for (InputDeviceBatteryListenerDelegate delegate : listenersForDevice.mDelegates) {
+ if (Objects.equals(listener, delegate.mListener)) {
+ throw new IllegalArgumentException(
+ "Attempting to register an InputDeviceBatteryListener that has "
+ + "already been registered for deviceId: "
+ + deviceId);
+ }
+ }
+ }
+ final InputDeviceBatteryListenerDelegate delegate =
+ new InputDeviceBatteryListenerDelegate(listener, executor);
+ listenersForDevice.mDelegates.add(delegate);
+
+ // Notify the listener immediately if we already have the latest battery state.
+ if (listenersForDevice.mLatestBatteryState != null) {
+ delegate.notifyBatteryStateChanged(listenersForDevice.mLatestBatteryState);
+ }
+ }
+ }
+
+ /**
+ * Removes a previously registered battery listener for an input device.
+ * @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+ * @hide
+ */
+ public void removeInputDeviceBatteryListener(int deviceId,
+ @NonNull InputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) {
+ return;
+ }
+ RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+ if (listenersForDevice == null) {
+ // The deviceId is not currently being monitored.
+ return;
+ }
+ final List<InputDeviceBatteryListenerDelegate> delegates =
+ listenersForDevice.mDelegates;
+ for (int i = 0; i < delegates.size();) {
+ if (Objects.equals(listener, delegates.get(i).mListener)) {
+ delegates.remove(i);
+ continue;
+ }
+ i++;
+ }
+ if (!delegates.isEmpty()) {
+ return;
+ }
+
+ // There are no more battery listeners for this deviceId. Stop monitoring this device.
+ mBatteryListeners.remove(deviceId);
+ try {
+ mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (mBatteryListeners.size() == 0) {
+ // There are no more devices being monitored, so the registered
+ // IInputDeviceBatteryListener will be automatically dropped by the server.
+ mBatteryListeners = null;
+ mInputDeviceBatteryListener = null;
+ }
+ }
+ }
+
+ /**
+ * A callback used to be notified about battery state changes for an input device. The
+ * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
+ * listener is successfully registered to provide the initial battery state of the device.
+ * @see InputDevice#getBatteryState()
+ * @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+ * @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+ * @hide
+ */
+ public interface InputDeviceBatteryListener {
+ /**
+ * Called when the battery state of an input device changes.
+ * @param deviceId the input device for which the battery changed.
+ * @param eventTimeMillis the time (in ms) when the battery change took place.
+ * This timestamp is in the {@link SystemClock#uptimeMillis()} time base.
+ * @param batteryState the new battery state, never null.
+ */
+ void onBatteryStateChanged(
+ int deviceId, long eventTimeMillis, @NonNull BatteryState batteryState);
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
@@ -1861,4 +1996,76 @@
}
}
}
+
+ private static final class LocalBatteryState extends BatteryState {
+ final int mDeviceId;
+ final boolean mIsPresent;
+ final int mStatus;
+ final float mCapacity;
+ final long mEventTime;
+
+ LocalBatteryState(int deviceId, boolean isPresent, int status, float capacity,
+ long eventTime) {
+ mDeviceId = deviceId;
+ mIsPresent = isPresent;
+ mStatus = status;
+ mCapacity = capacity;
+ mEventTime = eventTime;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return mIsPresent;
+ }
+
+ @Override
+ public int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public float getCapacity() {
+ return mCapacity;
+ }
+ }
+
+ private static final class RegisteredBatteryListeners {
+ final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
+ LocalBatteryState mLatestBatteryState;
+ }
+
+ private static final class InputDeviceBatteryListenerDelegate {
+ final InputDeviceBatteryListener mListener;
+ final Executor mExecutor;
+
+ InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ void notifyBatteryStateChanged(LocalBatteryState batteryState) {
+ mExecutor.execute(() ->
+ mListener.onBatteryStateChanged(batteryState.mDeviceId, batteryState.mEventTime,
+ batteryState));
+ }
+ }
+
+ private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
+ @Override
+ public void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status,
+ float capacity, long eventTime) {
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) return;
+ final RegisteredBatteryListeners entry = mBatteryListeners.get(deviceId);
+ if (entry == null) return;
+
+ entry.mLatestBatteryState =
+ new LocalBatteryState(
+ deviceId, isBatteryPresent, status, capacity, eventTime);
+ for (InputDeviceBatteryListenerDelegate delegate : entry.mDelegates) {
+ delegate.notifyBatteryStateChanged(entry.mLatestBatteryState);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 06c35b5..3d5c34c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -962,7 +962,7 @@
* also be bumped.
*/
static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility", "attention"
+ "other", "button", "touch", "accessibility", "attention", "faceDown", "deviceState"
};
public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3d70138..a7349f9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4388,6 +4388,9 @@
int type = readInt();
if (isLengthPrefixed(type)) {
int objectLength = readInt();
+ if (objectLength < 0) {
+ return null;
+ }
int end = MathUtils.addOrThrow(dataPosition(), objectLength);
int valueLength = end - start;
setDataPosition(end);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 13ca2c3..a46868e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -345,6 +345,39 @@
public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+ USER_ACTIVITY_EVENT_OTHER,
+ USER_ACTIVITY_EVENT_BUTTON,
+ USER_ACTIVITY_EVENT_TOUCH,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY,
+ USER_ACTIVITY_EVENT_ATTENTION,
+ USER_ACTIVITY_EVENT_FACE_DOWN,
+ USER_ACTIVITY_EVENT_DEVICE_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActivityEvent{}
+
+ /**
+ *
+ * Convert the user activity event to a string for debugging purposes.
+ * @hide
+ */
+ public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+ switch (userActivityEvent) {
+ case USER_ACTIVITY_EVENT_OTHER: return "other";
+ case USER_ACTIVITY_EVENT_BUTTON: return "button";
+ case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+ case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+ case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+ case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+ case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+ default: return Integer.toString(userActivityEvent);
+ }
+ }
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index fae72a2..ced2a01 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -20,13 +20,13 @@
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-import com.android.server.LocalServices;
-
/**
* Internal helper for launching dreams to ensure consistency between the
* <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> activity.
@@ -75,28 +75,32 @@
}
private static void startDream(Context context, boolean docked) {
- DreamManagerInternal dreamManagerService =
- LocalServices.getService(DreamManagerInternal.class);
- if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
- if (docked) {
- Slog.i(TAG, "Activating dream while docked.");
+ try {
+ IDreamManager dreamManagerService = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+ if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
+ if (docked) {
+ Slog.i(TAG, "Activating dream while docked.");
- // Wake up.
- // The power manager will wake up the system automatically when it starts
- // receiving power from a dock but there is a race between that happening
- // and the UI mode manager starting a dream. We want the system to already
- // be awake by the time this happens. Otherwise the dream may not start.
- PowerManager powerManager =
- context.getSystemService(PowerManager.class);
- powerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_PLUGGED_IN,
- "android.service.dreams:DREAM");
- } else {
- Slog.i(TAG, "Activating dream by user request.");
+ // Wake up.
+ // The power manager will wake up the system automatically when it starts
+ // receiving power from a dock but there is a race between that happening
+ // and the UI mode manager starting a dream. We want the system to already
+ // be awake by the time this happens. Otherwise the dream may not start.
+ PowerManager powerManager =
+ context.getSystemService(PowerManager.class);
+ powerManager.wakeUp(SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_PLUGGED_IN,
+ "android.service.dreams:DREAM");
+ } else {
+ Slog.i(TAG, "Activating dream by user request.");
+ }
+
+ // Dream.
+ dreamManagerService.dream();
}
-
- // Dream.
- dreamManagerService.requestDream();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Could not start dream when docked.", ex);
}
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 7d56039..7164412 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1022,6 +1022,15 @@
}
/**
+ * Reports whether the device has a battery.
+ * @return true if the device has a battery, false otherwise.
+ * @hide
+ */
+ public boolean hasBattery() {
+ return mHasBattery;
+ }
+
+ /**
* Provides information about the range of values for a particular {@link MotionEvent} axis.
*
* @see InputDevice#getMotionRange(int)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4caa38..91f36bf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1099,6 +1099,10 @@
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
}
+
+ // Update the last resource config in case the resource configuration was changed while
+ // activity relaunched.
+ mLastConfigurationFromResources.setTo(getConfiguration());
}
private Configuration getConfiguration() {
@@ -8154,6 +8158,7 @@
mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
mTempInsets, mTempControls, mRelayoutBundle);
mRelayoutRequested = true;
+
final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
@@ -8193,6 +8198,12 @@
}
}
+ if (mSurfaceControl.isValid() && !HardwareRenderer.isDrawingEnabled()) {
+ // When drawing is disabled the window layer won't have a valid buffer.
+ // Set a window crop so input can get delivered to the window.
+ mTransaction.setWindowCrop(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y).apply();
+ }
+
mLastTransformHint = transformHint;
mSurfaceControl.setTransformHint(transformHint);
@@ -8482,6 +8493,10 @@
if (mLocalSyncState != LOCAL_SYNC_NONE) {
writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
}
+ writer.println(innerPrefix + "mLastReportedMergedConfiguration="
+ + mLastReportedMergedConfiguration);
+ writer.println(innerPrefix + "mLastConfigurationFromResources="
+ + mLastConfigurationFromResources);
writer.println(innerPrefix + "mIsAmbientMode=" + mIsAmbientMode);
writer.println(innerPrefix + "mUnbufferedInputSource="
+ Integer.toHexString(mUnbufferedInputSource));
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 3c7cd02..36eaf49 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -51,16 +51,19 @@
in IWindowContainerTransactionCallback callback);
/**
- * Starts a transition.
+ * Starts a new transition.
* @param type The transition type.
- * @param transitionToken A token associated with the transition to start. If null, a new
- * transition will be created of the provided type.
* @param t Operations that are part of the transition.
- * @return a token representing the transition. This will just be transitionToken if it was
- * non-null.
+ * @return a token representing the transition.
*/
- IBinder startTransition(int type, in @nullable IBinder transitionToken,
- in @nullable WindowContainerTransaction t);
+ IBinder startNewTransition(int type, in @nullable WindowContainerTransaction t);
+
+ /**
+ * Starts the given transition.
+ * @param transitionToken A token associated with the transition to start.
+ * @param t Operations that are part of the transition.
+ */
+ oneway void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
/**
* Starts a legacy transition.
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index 64b2638..841354a 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -33,19 +33,19 @@
private final int mDisplayId;
- private final boolean mVisibleRequested;
+ private final boolean mVisible;
public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
- boolean visibleRequested) {
+ boolean visible) {
mConfiguration.setTo(configuration);
mDisplayId = displayId;
- mVisibleRequested = visibleRequested;
+ mVisible = visible;
}
public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.mDisplayId;
- mVisibleRequested = info.mVisibleRequested;
+ mVisible = info.mVisible;
}
/** The {@link Configuration} of the parent Task */
@@ -62,9 +62,9 @@
return mDisplayId;
}
- /** Whether the parent Task is requested to be visible or not */
- public boolean isVisibleRequested() {
- return mVisibleRequested;
+ /** Whether the parent Task is visible or not */
+ public boolean isVisible() {
+ return mVisible;
}
/**
@@ -80,7 +80,7 @@
return false;
}
return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
- && mVisibleRequested == that.mVisibleRequested;
+ && mVisible == that.mVisible;
}
@WindowConfiguration.WindowingMode
@@ -93,7 +93,7 @@
return TaskFragmentParentInfo.class.getSimpleName() + ":{"
+ "config=" + mConfiguration
+ ", displayId=" + mDisplayId
- + ", visibleRequested=" + mVisibleRequested
+ + ", visible=" + mVisible
+ "}";
}
@@ -114,14 +114,14 @@
final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
return mConfiguration.equals(that.mConfiguration)
&& mDisplayId == that.mDisplayId
- && mVisibleRequested == that.mVisibleRequested;
+ && mVisible == that.mVisible;
}
@Override
public int hashCode() {
int result = mConfiguration.hashCode();
result = 31 * result + mDisplayId;
- result = 31 * result + (mVisibleRequested ? 1 : 0);
+ result = 31 * result + (mVisible ? 1 : 0);
return result;
}
@@ -129,13 +129,13 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
dest.writeInt(mDisplayId);
- dest.writeBoolean(mVisibleRequested);
+ dest.writeBoolean(mVisible);
}
private TaskFragmentParentInfo(Parcel in) {
mConfiguration.readFromParcel(in);
mDisplayId = in.readInt();
- mVisibleRequested = in.readBoolean();
+ mVisible = in.readBoolean();
}
public static final Creator<TaskFragmentParentInfo> CREATOR =
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 641d1a1..fbdd325 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -132,8 +132,11 @@
*/
public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
+ /** This change happened underneath something else. */
+ public static final int FLAG_IS_OCCLUDED = 1 << 15;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 15;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 16;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -153,6 +156,7 @@
FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
FLAG_IS_BEHIND_STARTING_WINDOW,
+ FLAG_IS_OCCLUDED,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -362,6 +366,9 @@
if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
}
+ if ((flags & FLAG_IS_OCCLUDED) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 4ea5ea5..2a80d02 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -84,9 +84,8 @@
}
/**
- * Start a transition.
+ * Starts a new transition, don't use this to start an already created one.
* @param type The type of the transition. This is ignored if a transitionToken is provided.
- * @param transitionToken An existing transition to start. If null, a new transition is created.
* @param t The set of window operations that are part of this transition.
* @return A token identifying the transition. This will be the same as transitionToken if it
* was provided.
@@ -94,10 +93,24 @@
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@NonNull
- public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+ try {
+ return getWindowOrganizerController().startNewTransition(type, t);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts an already created transition.
+ * @param transitionToken An existing transition to start.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void startTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
try {
- return getWindowOrganizerController().startTransition(type, transitionToken, t);
+ getWindowOrganizerController().startTransition(transitionToken, t);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 5f4a9cd..473134e 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -172,14 +172,14 @@
@Override
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
- prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
+ TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
return true;
}
@Override
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
+ TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
@@ -224,13 +224,6 @@
}
};
- private static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
- final int currentUserId = UserHandle.myUserId();
- if (targetUserId != currentUserId) {
- intent.fixUris(currentUserId);
- }
- }
-
private DisplayResolveInfo(Parcel in) {
mDisplayLabel = in.readCharSequence();
mExtendedInfo = in.readCharSequence();
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 264e4f7..4b9b7cb 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -232,6 +232,7 @@
}
intent.setComponent(mChooserTarget.getComponentName());
intent.putExtras(mChooserTarget.getIntentExtras());
+ TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId);
// Important: we will ignore the target security checks in ActivityManager
// if and only if the ChooserTarget's target package is the same package
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
index f56ab17..7bb7ddc 100644
--- a/core/java/com/android/internal/app/chooser/TargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -130,4 +130,15 @@
* @return true if this target should be pinned to the front by the request of the user
*/
boolean isPinned();
+
+ /**
+ * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called
+ * before launching the intent as another user.
+ */
+ static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
+ final int currentUserId = UserHandle.myUserId();
+ if (targetUserId != currentUserId) {
+ intent.fixUris(currentUserId);
+ }
+ }
}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 627631a..d503904 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -115,7 +115,7 @@
Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
}
- mBrightnessSyncObserver.startObserving();
+ mBrightnessSyncObserver.startObserving(mHandler);
mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
}
@@ -482,30 +482,31 @@
}
};
- private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (selfChange) {
- return;
+ private ContentObserver createBrightnessContentObserver(Handler handler) {
+ return new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) {
+ return;
+ }
+ if (BRIGHTNESS_URI.equals(uri)) {
+ handleBrightnessChangeInt(getScreenBrightnessInt());
+ }
}
- if (BRIGHTNESS_URI.equals(uri)) {
- handleBrightnessChangeInt(getScreenBrightnessInt());
- }
- }
- };
+ };
+ }
boolean isObserving() {
return mIsObserving;
}
- void startObserving() {
+ void startObserving(Handler handler) {
final ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
- UserHandle.USER_ALL);
- mDisplayManager.registerDisplayListener(mListener, mHandler,
+ cr.registerContentObserver(BRIGHTNESS_URI, false,
+ createBrightnessContentObserver(handler), UserHandle.USER_ALL);
+ mDisplayManager.registerDisplayListener(mListener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
mIsObserving = true;
}
-
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ec2bc7c..8b96597 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 208;
+ static final int VERSION = 210;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -6145,12 +6145,13 @@
@UnsupportedAppUsage
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event) {
noteUserActivityLocked(uid, event, mClock.elapsedRealtime(), mClock.uptimeMillis());
}
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
+ long elapsedRealtimeMs, long uptimeMs) {
if (mOnBatteryInternal) {
uid = mapUid(uid);
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -9962,14 +9963,14 @@
}
@Override
- public void noteUserActivityLocked(int type) {
+ public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
- mUserActivityCounters[type].stepAtomic();
+ if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[event].stepAtomic();
} else {
- Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+ Slog.w(TAG, "Unknown user activity type " + event + " was specified.",
new Throwable());
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a4da8de4..1235b60 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1522,8 +1522,7 @@
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
- SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1576,12 +1575,6 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
- * Some authentication is required because the trustagent either timed out or was disabled
- * manually.
- */
- public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
-
- /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
new file mode 100644
index 0000000..e3b3ea7
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.input
+
+import android.hardware.BatteryState
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import com.android.server.testutils.any
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for [InputManager.InputDeviceBatteryListener].
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:InputDeviceBatteryListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class InputDeviceBatteryListenerTest {
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ private lateinit var testLooper: TestLooper
+ private var registeredListener: IInputDeviceBatteryListener? = null
+ private val monitoredDevices = mutableListOf<Int>()
+ private lateinit var executor: Executor
+ private lateinit var inputManager: InputManager
+
+ @Mock
+ private lateinit var iInputManagerMock: IInputManager
+
+ @Before
+ fun setUp() {
+ testLooper = TestLooper()
+ executor = HandlerExecutor(Handler(testLooper.looper))
+ registeredListener = null
+ monitoredDevices.clear()
+ inputManager = InputManager.resetInstance(iInputManagerMock)
+
+ // Handle battery listener registration.
+ doAnswer {
+ val deviceId = it.getArgument(0) as Int
+ val listener = it.getArgument(1) as IInputDeviceBatteryListener
+ if (registeredListener != null &&
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ // There can only be one registered battery listener per process.
+ fail("Trying to register a new listener when one already exists")
+ }
+ if (monitoredDevices.contains(deviceId)) {
+ fail("Trying to start monitoring a device that was already being monitored")
+ }
+ monitoredDevices.add(deviceId)
+ registeredListener = listener
+ null
+ }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
+
+ // Handle battery listener being unregistered.
+ doAnswer {
+ val deviceId = it.getArgument(0) as Int
+ val listener = it.getArgument(1) as IInputDeviceBatteryListener
+ if (registeredListener == null ||
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ fail("Trying to unregister a listener that is not registered")
+ }
+ if (!monitoredDevices.remove(deviceId)) {
+ fail("Trying to stop monitoring a device that is not being monitored")
+ }
+ if (monitoredDevices.isEmpty()) {
+ registeredListener = null
+ }
+ }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
+ }
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ private fun notifyBatteryStateChanged(
+ deviceId: Int,
+ isPresent: Boolean = true,
+ status: Int = BatteryState.STATUS_FULL,
+ capacity: Float = 1.0f,
+ eventTime: Long = 12345L
+ ) {
+ registeredListener!!.onBatteryStateChanged(deviceId, isPresent, status, capacity, eventTime)
+ }
+
+ @Test
+ fun testListenerIsNotifiedCorrectly() {
+ var callbackCount = 0
+
+ // Add a battery listener to monitor battery changes.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor) {
+ deviceId: Int, eventTime: Long, batteryState: BatteryState ->
+ callbackCount++
+ assertEquals(1, deviceId)
+ assertEquals(true, batteryState.isPresent)
+ assertEquals(BatteryState.STATUS_DISCHARGING, batteryState.status)
+ assertEquals(0.5f, batteryState.capacity)
+ assertEquals(8675309L, eventTime)
+ }
+
+ // Adding the listener should register the callback with InputManagerService.
+ assertNotNull(registeredListener)
+ assertTrue(monitoredDevices.contains(1))
+
+ // Notifying battery change for a different device should not trigger the listener.
+ notifyBatteryStateChanged(deviceId = 2)
+ testLooper.dispatchAll()
+ assertEquals(0, callbackCount)
+
+ // Notifying battery change for the registered device will notify the listener.
+ notifyBatteryStateChanged(1 /*deviceId*/, true /*isPresent*/,
+ BatteryState.STATUS_DISCHARGING, 0.5f /*capacity*/, 8675309L /*eventTime*/)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount)
+ }
+
+ @Test
+ fun testMultipleListeners() {
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+ val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+ // Monitor battery changes for three devices. The first callback monitors devices 1 and 3,
+ // while the second callback monitors devices 2 and 3.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+ assertEquals(1, monitoredDevices.size)
+ inputManager.addInputDeviceBatteryListener(2 /*deviceId*/, executor, callback2)
+ assertEquals(2, monitoredDevices.size)
+ inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback1)
+ assertEquals(3, monitoredDevices.size)
+ inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback2)
+ assertEquals(3, monitoredDevices.size)
+
+ // Notifying battery change for each of the devices should trigger the registered callbacks.
+ notifyBatteryStateChanged(deviceId = 1)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount1)
+ assertEquals(0, callbackCount2)
+
+ notifyBatteryStateChanged(deviceId = 2)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount1)
+ assertEquals(1, callbackCount2)
+
+ notifyBatteryStateChanged(deviceId = 3)
+ testLooper.dispatchNext()
+ testLooper.dispatchNext()
+ assertEquals(2, callbackCount1)
+ assertEquals(2, callbackCount2)
+
+ // Stop monitoring devices 1 and 2.
+ inputManager.removeInputDeviceBatteryListener(1 /*deviceId*/, callback1)
+ assertEquals(2, monitoredDevices.size)
+ inputManager.removeInputDeviceBatteryListener(2 /*deviceId*/, callback2)
+ assertEquals(1, monitoredDevices.size)
+
+ // Ensure device 3 continues to be monitored.
+ notifyBatteryStateChanged(deviceId = 3)
+ testLooper.dispatchNext()
+ testLooper.dispatchNext()
+ assertEquals(3, callbackCount1)
+ assertEquals(3, callbackCount2)
+
+ // Stop monitoring all devices.
+ inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback1)
+ assertEquals(1, monitoredDevices.size)
+ inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback2)
+ assertEquals(0, monitoredDevices.size)
+ }
+
+ @Test
+ fun testAdditionalListenersNotifiedImmediately() {
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+ val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+ // Add a battery listener and send the latest battery state.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+ assertEquals(1, monitoredDevices.size)
+ notifyBatteryStateChanged(deviceId = 1)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount1)
+
+ // Add a second listener for the same device that already has the latest battery state.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback2)
+ assertEquals(1, monitoredDevices.size)
+
+ // Ensure that this listener is notified immediately.
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount2)
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 91573ff..00943f2d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -140,7 +140,7 @@
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
- mIsVisible = info.isVisibleRequested();
+ mIsVisible = info.isVisible();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index c76f568..0fb6ff8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -103,14 +103,23 @@
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
* as a parameter.
+ *
+ * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all
+ * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo}
+ * together. However only the first registered consumer of a {@link Context} will actually
+ * invoke {@link #addWindowLayoutInfoListener(Context, Consumer)}.
+ * Here we enforce that {@link #addWindowLayoutInfoListener(Context, Consumer)} can only be
+ * called once for each {@link Context}.
*/
- // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+ @Override
public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
@NonNull Consumer<WindowLayoutInfo> consumer) {
if (mWindowLayoutChangeListeners.containsKey(context)
+ // In theory this method can be called on the same consumer with different context.
|| mWindowLayoutChangeListeners.containsValue(consumer)) {
- // Early return if the listener or consumer has been registered.
- return;
+ throw new IllegalArgumentException(
+ "Context or Consumer has already been registered for WindowLayoutInfo"
+ + " callback.");
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 2c766d8..b0b95f9 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec..82573b2 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,7 @@
srcs: [
"src/com/android/wm/shell/util/**/*.java",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+ "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
],
path: "src",
}
@@ -100,6 +101,21 @@
out: ["wm_shell_protolog.json"],
}
+genrule {
+ name: "protolog.json.gz",
+ srcs: [":generate-wm_shell_protolog.json"],
+ out: ["wmshell.protolog.json.gz"],
+ cmd: "$(location minigzip) -c < $(in) > $(out)",
+ tools: ["minigzip"],
+}
+
+prebuilt_etc {
+ name: "wmshell.protolog.json.gz",
+ system_ext_specific: true,
+ src: ":protolog.json.gz",
+ filename_from_src: true,
+}
+
// End ProtoLog
java_library {
@@ -123,9 +139,6 @@
resource_dirs: [
"res",
],
- java_resources: [
- ":generate-wm_shell_protolog.json",
- ],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 86f9d5b..8cbe44b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,13 +29,6 @@
public interface BackAnimation {
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 33ecdd8..938189f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -57,10 +58,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -105,6 +108,7 @@
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
private final ContentResolver mContentResolver;
+ private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@Nullable
@@ -231,21 +235,25 @@
public BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
- ActivityTaskManager.getService(), context, context.getContentResolver());
+ this(shellInit, shellController, shellExecutor, backgroundHandler,
+ new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
+ context, context.getContentResolver());
}
@VisibleForTesting
BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
+ mShellController = shellController;
mShellExecutor = shellExecutor;
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
@@ -257,6 +265,8 @@
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+ this::createExternalInterface, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -289,7 +299,11 @@
return mBackAnimation;
}
- private final BackAnimation mBackAnimation = new BackAnimationImpl();
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IBackAnimationImpl(this);
+ }
+
+ private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
@Override
public Context getContext() {
@@ -302,17 +316,6 @@
}
private class BackAnimationImpl implements BackAnimation {
- private IBackAnimationImpl mBackAnimation;
-
- @Override
- public IBackAnimation createExternalInterface() {
- if (mBackAnimation != null) {
- mBackAnimation.invalidate();
- }
- mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
- return mBackAnimation;
- }
-
@Override
public void onBackMotion(
float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -331,7 +334,8 @@
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub {
+ private static class IBackAnimationImpl extends IBackAnimation.Stub
+ implements ExternalInterfaceBinder {
private BackAnimationController mController;
IBackAnimationImpl(BackAnimationController controller) {
@@ -356,7 +360,8 @@
(controller) -> controller.onBackToLauncherAnimationFinished());
}
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
new file mode 100644
index 0000000..aa5b0cb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import android.os.IBinder;
+
+/**
+ * An interface for binders which can be registered to be sent to other processes.
+ */
+public interface ExternalInterfaceBinder {
+ /**
+ * Invalidates this binder (detaches it from the controller it would call).
+ */
+ void invalidate();
+
+ /**
+ * Returns the IBinder to send.
+ */
+ IBinder asBinder();
+}
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 7c9cabd..28a1959 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
@@ -261,13 +261,14 @@
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
- context));
+ new BackAnimationController(shellInit, shellController, shellExecutor,
+ backgroundHandler, context));
}
return Optional.empty();
}
@@ -292,17 +293,17 @@
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FullscreenTaskListener<?> optionalFullscreenTaskListener();
+ abstract FullscreenTaskListener optionalFullscreenTaskListener();
@WMSingleton
@Provides
- static FullscreenTaskListener<?> provideFullscreenTaskListener(
- @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener,
+ static FullscreenTaskListener provideFullscreenTaskListener(
+ @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional,
- Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) {
+ Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
@@ -316,7 +317,7 @@
//
@BindsOptionalOf
- abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+ abstract WindowDecorViewModel optionalWindowDecorViewModel();
//
// Unfold transition
@@ -471,6 +472,7 @@
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -478,9 +480,9 @@
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellController,
+ shellCommandHandler, taskStackListener, activityTaskManager,
+ desktopModeTaskRepository, mainExecutor));
}
//
@@ -497,14 +499,15 @@
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor);
+ return new Transitions(context, shellInit, shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor);
}
@WMSingleton
@@ -621,13 +624,15 @@
@WMSingleton
@Provides
- static StartingWindowController provideStartingWindowController(Context context,
+ static StartingWindowController provideStartingWindowController(
+ Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
@@ -768,7 +773,7 @@
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener<?> fullscreenTaskListener,
+ FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 47b6659..1ec98d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -55,7 +55,6 @@
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -183,7 +182,7 @@
@WMSingleton
@Provides
- static WindowDecorViewModel<?> provideWindowDecorViewModel(
+ static WindowDecorViewModel provideWindowDecorViewModel(
Context context,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
@@ -209,7 +208,7 @@
@Provides
@DynamicOverride
static FreeformComponents provideFreeformComponents(
- FreeformTaskListener<?> taskListener,
+ FreeformTaskListener taskListener,
FreeformTaskTransitionHandler transitionHandler,
FreeformTaskTransitionObserver transitionObserver) {
return new FreeformComponents(
@@ -218,18 +217,18 @@
@WMSingleton
@Provides
- static FreeformTaskListener<?> provideFreeformTaskListener(
+ static FreeformTaskListener provideFreeformTaskListener(
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
ShellInit init = FreeformComponents.isFreeformEnabled(context)
? shellInit
: null;
- return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
+ return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository,
windowDecorViewModel);
}
@@ -238,7 +237,7 @@
static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
}
@@ -248,10 +247,9 @@
Context context,
ShellInit shellInit,
Transitions transitions,
- FullscreenTaskListener<?> fullscreenTaskListener,
- FreeformTaskListener<?> freeformTaskListener) {
+ WindowDecorViewModel windowDecorViewModel) {
return new FreeformTaskTransitionObserver(
- context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
+ context, shellInit, transitions, windowDecorViewModel);
}
//
@@ -600,7 +598,9 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
+ static DesktopModeController provideDesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -608,7 +608,7 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index ff3be38..44a467f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -23,9 +23,4 @@
*/
@ExternalThread
public interface DesktopMode {
-
- /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
- default IDesktopMode createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 99739c4..b96facf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
@@ -29,23 +31,30 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -55,18 +64,22 @@
/**
* Handles windowing changes when desktop mode system setting changes
*/
-public class DesktopModeController implements RemoteCallable<DesktopModeController> {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+ Transitions.TransitionHandler {
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final Transitions mTransitions;
private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
- private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+ private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
private final SettingsObserver mSettingsObserver;
- public DesktopModeController(Context context, ShellInit shellInit,
+ public DesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -74,6 +87,7 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTransitions = transitions;
@@ -85,10 +99,13 @@
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+ this::createExternalInterface, this);
mSettingsObserver.observe();
if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
+ mTransitions.addHandler(this);
}
@Override
@@ -108,6 +125,13 @@
return mDesktopModeImpl;
}
+ /**
+ * Creates a new instance of the external interface to pass to another process.
+ */
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IDesktopModeImpl(this);
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -157,7 +181,7 @@
/**
* Show apps on desktop
*/
- public void showDesktopApps() {
+ WindowContainerTransaction showDesktopApps() {
ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -173,7 +197,12 @@
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
- mShellTaskOrganizer.applyTransaction(wct);
+
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+
+ return wct;
}
/**
@@ -195,6 +224,35 @@
.configuration.windowConfiguration.getWindowingMode();
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+
+ // Only do anything if we are in desktop mode and opening a task/app
+ if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+ return null;
+ }
+
+ WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ wct.merge(showDesktopApps(), true /* transfer */);
+ wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+ return wct;
+ }
+
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
@@ -235,24 +293,15 @@
*/
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
-
- private IDesktopModeImpl mIDesktopMode;
-
- @Override
- public IDesktopMode createExternalInterface() {
- if (mIDesktopMode != null) {
- mIDesktopMode.invalidate();
- }
- mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
- return mIDesktopMode;
- }
+ // Do nothing
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub {
+ private static class IDesktopModeImpl extends IDesktopMode.Stub
+ implements ExternalInterfaceBinder {
private DesktopModeController mController;
@@ -263,7 +312,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d..fbf326e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@
### SysUI accessible components
In addition to doing the above, you will also need to provide an interface for calling to SysUI
from the Shell and vice versa. The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
`SysUIComponent` to take the interface so it can be injected in SysUI code. The binding between
the two is done in `SystemUIFactory#init()` which will need to be updated as well.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+ controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
### Launcher accessible components
Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
Launcher requires a new AIDL interface to be created and implemented by the controller. The
implementation of the stub interface in the controller otherwise behaves similar to the interface
to SysUI where it posts the work to the main Shell thread.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+ extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+ references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+ the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+ and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+ call the SystemUIProxy method as needed in that code
+
### Component initialization
To initialize the component:
- On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@
### General Do's & Dont's
Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
Don't:
- **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
index 9356660..f86d467 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
@@ -33,9 +33,4 @@
* - If there is a floating task for this intent, and it's not stashed, this stashes it.
*/
void showOrSetStashed(Intent intent);
-
- /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */
- default IFloatingTasks createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
index 6755299..b3c09d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
import android.annotation.Nullable;
import android.content.Context;
@@ -40,6 +41,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -136,11 +138,13 @@
if (isFloatingTasksEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
- mShellCommandHandler.addDumpCallback(this::dump, this);
}
protected void onInit() {
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
+ this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/** Only used for testing. */
@@ -168,6 +172,10 @@
return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IFloatingTasksImpl(this);
+ }
+
@Override
public void onThemeChanged() {
if (mIsFloatingLayerAdded) {
@@ -412,28 +420,18 @@
*/
@ExternalThread
private class FloatingTaskImpl implements FloatingTasks {
- private IFloatingTasksImpl mIFloatingTasks;
-
@Override
public void showOrSetStashed(Intent intent) {
mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
}
-
- @Override
- public IFloatingTasks createExternalInterface() {
- if (mIFloatingTasks != null) {
- mIFloatingTasks.invalidate();
- }
- mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this);
- return mIFloatingTasks;
- }
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IFloatingTasksImpl extends IFloatingTasks.Stub {
+ private static class IFloatingTasksImpl extends IFloatingTasks.Stub
+ implements ExternalInterfaceBinder {
private FloatingTasksController mController;
IFloatingTasksImpl(FloatingTasksController controller) {
@@ -443,7 +441,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index e2d5a49..f82a346 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -19,12 +19,8 @@
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -41,31 +37,26 @@
/**
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
- *
- * @param <T> the type of window decoration instance
*/
-public class FreeformTaskListener<T extends AutoCloseable>
- implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FreeformTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
- private final WindowDecorViewModel<T> mWindowDecorationViewModel;
+ private final WindowDecorViewModel mWindowDecorationViewModel;
- private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+ private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State<T extends AutoCloseable> {
+ private static class State {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
- T mWindowDecoration;
}
public FreeformTaskListener(
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
- WindowDecorViewModel<T> windowDecorationViewModel) {
+ WindowDecorViewModel windowDecorationViewModel) {
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -80,13 +71,18 @@
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mTasks.get(taskInfo.taskId) != null) {
+ throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
taskInfo.taskId);
- final State<T> state = createOrUpdateTaskState(taskInfo, leash);
+ final State state = new State();
+ state.mTaskInfo = taskInfo;
+ state.mLeash = leash;
+ mTasks.put(taskInfo.taskId, state);
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- state.mWindowDecoration =
- mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+ mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
t.apply();
}
@@ -97,28 +93,8 @@
}
}
- private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- updateTaskInfo(taskInfo);
- return state;
- }
-
- state = new State<>();
- state.mTaskInfo = taskInfo;
- state.mLeash = leash;
- mTasks.put(taskInfo.taskId, state);
-
- return state;
- }
-
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- // This is possible if the transition happens before this method.
- return;
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
@@ -129,26 +105,18 @@
mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
}
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Save window decorations of closing tasks so that we can hand them over to the
- // transition system if this method happens before the transition. In case where the
- // transition didn't happen, it'd be cleared when the next transition finished.
- if (state.mWindowDecoration != null) {
- mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
- }
- return;
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
}
- releaseWindowDecor(state.mWindowDecoration);
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final State<T> state = updateTaskInfo(taskInfo);
+ final State state = mTasks.get(taskInfo.taskId);
+ state.mTaskInfo = taskInfo;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
- if (state.mWindowDecoration != null) {
- mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
- }
+ mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
if (DesktopModeStatus.IS_SUPPORTED) {
if (taskInfo.isVisible) {
@@ -159,15 +127,6 @@
}
}
- private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
- }
- state.mTaskInfo = taskInfo;
- return state;
- }
-
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
@@ -186,103 +145,6 @@
return mTasks.get(taskId).mLeash;
}
- /**
- * Creates a window decoration for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @return {@code true} if it creates the window decoration; {@code false} otherwise
- */
- boolean createWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- if (state.mWindowDecoration != null) {
- return false;
- }
- state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- return true;
- }
-
- /**
- * Gives out the ownership of the task's window decoration. The given task is leaving (of has
- * left) this task listener. This is the transition system asking for the ownership.
- *
- * @param taskInfo the maximizing task
- * @return the window decor of the maximizing task if any
- */
- T giveWindowDecoration(
- RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- T windowDecor;
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- windowDecor = state.mWindowDecoration;
- state.mWindowDecoration = null;
- } else {
- windowDecor =
- mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
- }
- if (windowDecor == null) {
- return null;
- }
- mWindowDecorationViewModel.setupWindowDecorationForTransition(
- taskInfo, startT, finishT, windowDecor);
- return windowDecor;
- }
-
- /**
- * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @param startT the start transaction of this transition
- * @param finishT the finish transaction of this transition
- * @param windowDecor the window decoration to adopt
- * @return {@code true} if it adopts the window decoration; {@code false} otherwise
- */
- boolean adoptWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @Nullable AutoCloseable windowDecor) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
- if (state.mWindowDecoration != null) {
- mWindowDecorationViewModel.setupWindowDecorationForTransition(
- state.mTaskInfo, startT, finishT, state.mWindowDecoration);
- return true;
- } else {
- state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- return false;
- }
- }
-
- void onTaskTransitionFinished() {
- if (mWindowDecorOfVanishedTasks.size() == 0) {
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Clearing window decors of vanished tasks. There could be visual defects "
- + "if any of them is used later in transitions.");
- for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
- releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
- }
- mWindowDecorOfVanishedTasks.clear();
- }
-
- private void releaseWindowDecor(T windowDecor) {
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
- }
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index fd4c85fa..04fc79a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -46,14 +46,14 @@
implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
private final Transitions mTransitions;
- private final WindowDecorViewModel<?> mWindowDecorViewModel;
+ private final WindowDecorViewModel mWindowDecorViewModel;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
public FreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
mTransitions = transitions;
mWindowDecorViewModel = windowDecorViewModel;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 17d6067..f4888fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -16,13 +16,9 @@
package com.android.wm.shell.freeform;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
import android.app.ActivityManager;
import android.content.Context;
import android.os.IBinder;
-import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -31,9 +27,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,23 +43,19 @@
* be a part of transitions.
*/
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
- private static final String TAG = "FreeformTO";
-
private final Transitions mTransitions;
- private final FreeformTaskListener<?> mFreeformTaskListener;
- private final FullscreenTaskListener<?> mFullscreenTaskListener;
+ private final WindowDecorViewModel mWindowDecorViewModel;
- private final Map<IBinder, List<AutoCloseable>> mTransitionToWindowDecors = new HashMap<>();
+ private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
+ new HashMap<>();
public FreeformTaskTransitionObserver(
Context context,
ShellInit shellInit,
Transitions transitions,
- FullscreenTaskListener<?> fullscreenTaskListener,
- FreeformTaskListener<?> freeformTaskListener) {
+ WindowDecorViewModel windowDecorViewModel) {
mTransitions = transitions;
- mFreeformTaskListener = freeformTaskListener;
- mFullscreenTaskListener = fullscreenTaskListener;
+ mWindowDecorViewModel = windowDecorViewModel;
if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -80,7 +72,7 @@
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
- final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
+ final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
@@ -110,92 +102,40 @@
onOpenTransitionReady(change, startT, finishT);
break;
case WindowManager.TRANSIT_CLOSE: {
- onCloseTransitionReady(change, windowDecors, startT, finishT);
+ taskInfoList.add(change.getTaskInfo());
+ onCloseTransitionReady(change, startT, finishT);
break;
}
case WindowManager.TRANSIT_CHANGE:
- onChangeTransitionReady(info.getType(), change, startT, finishT);
+ onChangeTransitionReady(change, startT, finishT);
break;
}
}
- if (!windowDecors.isEmpty()) {
- mTransitionToWindowDecors.put(transition, windowDecors);
- }
+ mTransitionToTaskInfo.put(transition, taskInfoList);
}
private void onOpenTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- switch (change.getTaskInfo().getWindowingMode()){
- case WINDOWING_MODE_FREEFORM:
- mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
- break;
- case WINDOWING_MODE_FULLSCREEN:
- mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
- break;
- }
+ mWindowDecorViewModel.createWindowDecoration(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onCloseTransitionReady(
TransitionInfo.Change change,
- ArrayList<AutoCloseable> windowDecors,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- final AutoCloseable windowDecor;
- switch (change.getTaskInfo().getWindowingMode()) {
- case WINDOWING_MODE_FREEFORM:
- windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
- startT, finishT);
- break;
- case WINDOWING_MODE_FULLSCREEN:
- windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
- startT, finishT);
- break;
- default:
- windowDecor = null;
- }
- if (windowDecor != null) {
- windowDecors.add(windowDecor);
- }
+ mWindowDecorViewModel.setupWindowDecorationForTransition(
+ change.getTaskInfo(), startT, finishT);
}
private void onChangeTransitionReady(
- int type,
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- AutoCloseable windowDecor = null;
-
- boolean adopted = false;
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- windowDecor = mFreeformTaskListener.giveWindowDecoration(
- change.getTaskInfo(), startT, finishT);
- if (windowDecor != null) {
- adopted = mFullscreenTaskListener.adoptWindowDecoration(
- change, startT, finishT, windowDecor);
- } else {
- // will return false if it already has the window decor.
- adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
- }
- }
-
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- windowDecor = mFullscreenTaskListener.giveWindowDecoration(
- change.getTaskInfo(), startT, finishT);
- if (windowDecor != null) {
- adopted = mFreeformTaskListener.adoptWindowDecoration(
- change, startT, finishT, windowDecor);
- } else {
- // will return false if it already has the window decor.
- adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
- }
- }
-
- if (!adopted) {
- releaseWindowDecor(windowDecor);
- }
+ mWindowDecorViewModel.setupWindowDecorationForTransition(
+ change.getTaskInfo(), startT, finishT);
}
@Override
@@ -203,43 +143,32 @@
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- final List<AutoCloseable> windowDecorsOfMerged = mTransitionToWindowDecors.get(merged);
- if (windowDecorsOfMerged == null) {
+ final List<ActivityManager.RunningTaskInfo> infoOfMerged =
+ mTransitionToTaskInfo.get(merged);
+ if (infoOfMerged == null) {
// We are adding window decorations of the merged transition to them of the playing
// transition so if there is none of them there is nothing to do.
return;
}
- mTransitionToWindowDecors.remove(merged);
+ mTransitionToTaskInfo.remove(merged);
- final List<AutoCloseable> windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing);
- if (windowDecorsOfPlaying != null) {
- windowDecorsOfPlaying.addAll(windowDecorsOfMerged);
+ final List<ActivityManager.RunningTaskInfo> infoOfPlaying =
+ mTransitionToTaskInfo.get(playing);
+ if (infoOfPlaying != null) {
+ infoOfPlaying.addAll(infoOfMerged);
} else {
- mTransitionToWindowDecors.put(playing, windowDecorsOfMerged);
+ mTransitionToTaskInfo.put(playing, infoOfMerged);
}
}
@Override
public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
- final List<AutoCloseable> windowDecors = mTransitionToWindowDecors.getOrDefault(
- transition, Collections.emptyList());
- mTransitionToWindowDecors.remove(transition);
+ final List<ActivityManager.RunningTaskInfo> taskInfo =
+ mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
+ mTransitionToTaskInfo.remove(transition);
- for (AutoCloseable windowDecor : windowDecors) {
- releaseWindowDecor(windowDecor);
- }
- mFullscreenTaskListener.onTaskTransitionFinished();
- mFreeformTaskListener.onTaskTransitionFinished();
- }
-
- private static void releaseWindowDecor(AutoCloseable windowDecor) {
- if (windowDecor == null) {
- return;
- }
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
+ for (int i = 0; i < taskInfo.size(); ++i) {
+ mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 76e296b..75a4091 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -22,13 +22,10 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.graphics.Point;
-import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,23 +43,20 @@
* Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
* @param <T> the type of window decoration instance
*/
-public class FullscreenTaskListener<T extends AutoCloseable>
- implements ShellTaskOrganizer.TaskListener {
+public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+ private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State<T extends AutoCloseable> {
+ private static class State {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
- T mWindowDecoration;
}
private final SyncTransactionQueue mSyncQueue;
private final Optional<RecentTasksController> mRecentTasksOptional;
- private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional;
+ private final Optional<WindowDecorViewModel> mWindowDecorViewModelOptional;
/**
* This constructor is used by downstream products.
*/
@@ -75,7 +69,7 @@
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional,
- Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) {
+ Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
mShellTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mRecentTasksOptional = recentTasksOptional;
@@ -98,21 +92,21 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
- final State<T> state = new State();
+ final State state = new State();
state.mLeash = leash;
state.mTaskInfo = taskInfo;
mTasks.put(taskInfo.taskId, state);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
+ boolean createdWindowDecor = false;
if (mWindowDecorViewModelOptional.isPresent()) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- state.mWindowDecoration =
- mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
- leash, t, t);
+ createdWindowDecor = mWindowDecorViewModelOptional.get()
+ .createWindowDecoration(taskInfo, leash, t, t);
t.apply();
}
- if (state.mWindowDecoration == null) {
+ if (!createdWindowDecor) {
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
@@ -127,12 +121,11 @@
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
+ final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
state.mTaskInfo = taskInfo;
- if (state.mWindowDecoration != null) {
- mWindowDecorViewModelOptional.get().onTaskInfoChanged(
- state.mTaskInfo, state.mWindowDecoration);
+ if (mWindowDecorViewModelOptional.isPresent()) {
+ mWindowDecorViewModelOptional.get().onTaskInfoChanged(state.mTaskInfo);
}
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
@@ -147,160 +140,13 @@
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- // This is possible if the transition happens before this method.
- return;
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Save window decorations of closing tasks so that we can hand them over to the
- // transition system if this method happens before the transition. In case where the
- // transition didn't happen, it'd be cleared when the next transition finished.
- if (state.mWindowDecoration != null) {
- mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
- }
- return;
- }
- releaseWindowDecor(state.mWindowDecoration);
- }
-
- /**
- * Creates a window decoration for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @return {@code true} if a decoration was actually created.
- */
- public boolean createWindowDecoration(TransitionInfo.Change change,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- if (!mWindowDecorViewModelOptional.isPresent()) return false;
- if (state.mWindowDecoration != null) {
- // Already has a decoration.
- return false;
- }
- T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- if (newWindowDecor != null) {
- state.mWindowDecoration = newWindowDecor;
- return true;
- }
- return false;
- }
-
- /**
- * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @param startT the start transaction of this transition
- * @param finishT the finish transaction of this transition
- * @param windowDecor the window decoration to adopt
- * @return {@code true} if it adopts the window decoration; {@code false} otherwise
- */
- public boolean adoptWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @Nullable AutoCloseable windowDecor) {
- if (!mWindowDecorViewModelOptional.isPresent()) {
- return false;
- }
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration(
- windowDecor);
- if (state.mWindowDecoration != null) {
- mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
- state.mTaskInfo, startT, finishT, state.mWindowDecoration);
- return true;
- } else {
- T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- if (newWindowDecor != null) {
- state.mWindowDecoration = newWindowDecor;
- }
- return false;
- }
- }
-
- /**
- * Clear window decors of vanished tasks.
- */
- public void onTaskTransitionFinished() {
- if (mWindowDecorOfVanishedTasks.size() == 0) {
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Clearing window decors of vanished tasks. There could be visual defects "
- + "if any of them is used later in transitions.");
- for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
- releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
- }
- mWindowDecorOfVanishedTasks.clear();
- }
-
- /**
- * Gives out the ownership of the task's window decoration. The given task is leaving (of has
- * left) this task listener. This is the transition system asking for the ownership.
- *
- * @param taskInfo the maximizing task
- * @return the window decor of the maximizing task if any
- */
- public T giveWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- T windowDecor;
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- windowDecor = state.mWindowDecoration;
- state.mWindowDecoration = null;
- } else {
- windowDecor =
- mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
- }
- if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) {
- mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
- taskInfo, startT, finishT, windowDecor);
- }
-
- return windowDecor;
- }
-
- private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl leash) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- updateTaskInfo(taskInfo);
- return state;
- }
-
- state = new State<T>();
- state.mTaskInfo = taskInfo;
- state.mLeash = leash;
- mTasks.put(taskInfo.taskId, state);
-
- return state;
- }
-
- private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- state.mTaskInfo = taskInfo;
- return state;
- }
-
- private void releaseWindowDecor(T windowDecor) {
- if (windowDecor == null) {
- return;
- }
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ if (mWindowDecorViewModelOptional.isPresent()) {
+ mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
}
}
@@ -342,6 +188,4 @@
public String toString() {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
-
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 7129165..2ee3348 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -30,13 +30,6 @@
OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
/**
- * Returns a binder that can be passed to an external process to manipulate OneHanded.
- */
- default IOneHanded createExternalInterface() {
- return null;
- }
-
- /**
* Enters one handed mode.
*/
void startOneHanded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e0c4fe8..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -24,6 +24,7 @@
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import android.annotation.BinderThread;
import android.content.ComponentName;
@@ -49,6 +50,7 @@
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+ this::createExternalInterface, this);
}
public OneHanded asOneHanded() {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IOneHandedImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -709,17 +717,6 @@
*/
@ExternalThread
private class OneHandedImpl implements OneHanded {
- private IOneHandedImpl mIOneHanded;
-
- @Override
- public IOneHanded createExternalInterface() {
- if (mIOneHanded != null) {
- mIOneHanded.invalidate();
- }
- mIOneHanded = new IOneHandedImpl(OneHandedController.this);
- return mIOneHanded;
- }
-
@Override
public void startOneHanded() {
mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IOneHandedImpl extends IOneHanded.Stub {
+ private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
private OneHandedController mController;
IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 72b9dd3..f34d2a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -27,14 +27,6 @@
*/
@ExternalThread
public interface Pip {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate PIP.
- */
- default IPip createExternalInterface() {
- return null;
- }
-
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 3345b1b..a918559 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -33,6 +33,7 @@
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -68,6 +69,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -632,6 +634,12 @@
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
+ }
+
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
}
@Override
@@ -1040,17 +1048,6 @@
* The interface for calls from outside the Shell, within the host process.
*/
private class PipImpl implements Pip {
- private IPipImpl mIPip;
-
- @Override
- public IPip createExternalInterface() {
- if (mIPip != null) {
- mIPip.invalidate();
- }
- mIPip = new IPipImpl(PipController.this);
- return mIPip;
- }
-
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
@@ -1098,7 +1095,7 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IPipImpl extends IPip.Stub {
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
private PipController mController;
private final SingleInstanceRemoteListener<PipController,
IPipAnimationListener> mListener;
@@ -1129,7 +1126,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
package com.android.wm.shell.protolog;
import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.BaseProtoLogImpl;
import com.android.internal.protolog.ProtoLogViewerConfigReader;
import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
-import org.json.JSONException;
-
/**
* A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: Get the right path for the proto log file when we initialize the shell components
- private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+ // TODO: find a proper location to save the protolog message file
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+ private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
private static ShellProtoLogImpl sServiceInstance = null;
@@ -111,18 +104,8 @@
}
public int startTextLogging(String[] groups, PrintWriter pw) {
- try (InputStream is =
- getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
- mViewerConfig.loadViewerConfig(is);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- } catch (IOException e) {
- Log.i(TAG, "Unable to load log definitions: IOException while reading "
- + "wm_shell_protolog. " + e);
- } catch (JSONException e) {
- Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
- + "wm_shell_protolog. " + e);
- }
- return -1;
+ mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+ return setLogging(true /* setTextLogging */, true, pw, groups);
}
public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@
}
private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+ super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+ new ProtoLogViewerConfigReader());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2a62552..069066e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -29,13 +29,6 @@
@ExternalThread
public interface RecentTasks {
/**
- * Returns a binder that can be passed to an external process to fetch recent tasks.
- */
- default IRecentTasks createExternalInterface() {
- return null;
- }
-
- /**
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 02b5a35..08f3db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -37,6 +38,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -48,6 +50,7 @@
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -69,11 +72,12 @@
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
- private final RecentTasks mImpl = new RecentTasksImpl();
+ private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -97,6 +101,7 @@
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -106,18 +111,20 @@
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
- activityTaskManager, desktopModeTaskRepository, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+ taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
@@ -131,7 +138,13 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IRecentTasksImpl(this);
+ }
+
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
@@ -366,17 +379,6 @@
*/
@ExternalThread
private class RecentTasksImpl implements RecentTasks {
- private IRecentTasksImpl mIRecentTasks;
-
- @Override
- public IRecentTasks createExternalInterface() {
- if (mIRecentTasks != null) {
- mIRecentTasks.invalidate();
- }
- mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
- return mIRecentTasks;
- }
-
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
@@ -393,7 +395,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IRecentTasksImpl extends IRecentTasks.Stub {
+ private static class IRecentTasksImpl extends IRecentTasks.Stub
+ implements ExternalInterfaceBinder {
private RecentTasksController mController;
private final SingleInstanceRemoteListener<RecentTasksController,
IRecentTasksListener> mListener;
@@ -424,7 +427,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index ecdafa9..eb08d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -79,26 +79,47 @@
/**
* Starts tasks simultaneously in one transition.
*/
- oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 10;
+ oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteTransition remoteTransition,
+ in InstanceId instanceId) = 10;
+
+ /**
+ * Starts a pair of intent and task in one transition.
+ */
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
+ in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
+
+ /**
+ * Starts a pair of shortcut and task in one transition.
+ */
+ oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
*/
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
+ oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
- int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
in InstanceId instanceId) = 12;
/**
+ * Starts a pair of shortcut and task using legacy transition system.
+ */
+ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -111,11 +132,5 @@
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
-
- /**
- * Starts a pair of shortcut and task using legacy transition system.
- */
- oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
- in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
}
+// Last id = 17
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e73b799..d86aadc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -70,13 +70,6 @@
/** Unregisters listener that gets split screen callback. */
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 07a6895..c6a2b83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -29,6 +29,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
@@ -71,6 +72,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -214,6 +216,10 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new ISplitScreenImpl(this);
+ }
+
/**
* This will be called after ShellTaskOrganizer has initialized/registered because of the
* dependency order.
@@ -224,6 +230,8 @@
mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ this::createExternalInterface, this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
@@ -658,7 +666,6 @@
*/
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
@Override
@@ -704,15 +711,6 @@
};
@Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -752,7 +750,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private static class ISplitScreenImpl extends ISplitScreen.Stub
+ implements ExternalInterfaceBinder {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
@@ -779,7 +778,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
@@ -828,47 +828,68 @@
}
@Override
- public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ taskId1, options1, taskId2, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
- int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
+ int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
- sidePosition, splitRatio, adapter, instanceId));
+ pendingIntent, fillInIntent, options1, taskId, options2,
+ splitPosition, splitRatio, adapter, instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
- shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
+ shortcutInfo, options1, taskId, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
- public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio,
+ public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition,
+ (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
+ taskId2, options2, splitPosition, splitRatio, remoteTransition,
+ instanceId));
+ }
+
+ @Override
+ public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
+ (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
+ fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
+ remoteTransition, instanceId));
+ }
+
+ @Override
+ public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
+ (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
+ options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
instanceId));
}
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 c17f822..9102bd3 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
@@ -516,14 +516,55 @@
}
/** Starts 2 tasks in one transition. */
- void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mainOptions = mainOptions != null ? mainOptions : new Bundle();
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
+ startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Start an intent and a task to a split pair in one transition. */
+ void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Starts a shortcut and a task to a split pair in one transition. */
+ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /**
+ * Starts with the second task to a split pair in one transition.
+ *
+ * @param wct transaction to start the first task
+ * @param instanceId if {@code null}, will not log. Otherwise it will be used in
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ */
+ private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
if (mMainStage.isActive()) {
mMainStage.evictAllChildren(wct);
mSideStage.evictAllChildren(wct);
@@ -538,60 +579,61 @@
wct.setForceTranslucent(mRootTaskInfo.token, false);
// Make sure the launch options will put tasks in the corresponding split roots
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
mSplitTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
setEnterInstanceId(instanceId);
}
- /** Starts 2 tasks in one legacy transition. */
- void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ /** Starts a pair of tasks using legacy transition. */
+ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startTask(sideTaskId, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
- startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
- /** Start an intent and a task ordered by {@code intentFirst}. */
+ /** Starts a pair of intent and task using legacy transition. */
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
+ /** Starts a pair of shortcut and task using legacy transition. */
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
/**
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -1727,6 +1769,7 @@
@StageType
private int getStageType(StageTaskListener stage) {
+ if (stage == null) return STAGE_TYPE_UNDEFINED;
return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
}
@@ -1981,8 +2024,8 @@
}
}
- // TODO: fallback logic. Probably start a new transition to exit split before applying
- // anything here. Ideally consolidate with transition-merging.
+ // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
throw new IllegalStateException("Launched a task in split, but didn't receive any"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a3..538bbec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@
* Interface to engage starting window feature.
*/
public interface StartingSurface {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate starting windows.
- */
- default IStartingWindow createExternalInterface() {
- return null;
- }
-
/**
* Returns the background color for a starting window if existing.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 379af21..0c23f10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,6 +23,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -43,10 +44,12 @@
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
/**
@@ -76,6 +79,7 @@
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
@@ -86,12 +90,14 @@
public StartingWindowController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
@@ -107,8 +113,14 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IStartingWindowImpl(this);
+ }
+
private void onInit() {
mShellTaskOrganizer.initStartingWindow(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+ this::createExternalInterface, this);
}
@Override
@@ -222,17 +234,6 @@
* The interface for calls from outside the Shell, within the host process.
*/
private class StartingSurfaceImpl implements StartingSurface {
- private IStartingWindowImpl mIStartingWindow;
-
- @Override
- public IStartingWindowImpl createExternalInterface() {
- if (mIStartingWindow != null) {
- mIStartingWindow.invalidate();
- }
- mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
- return mIStartingWindow;
- }
-
@Override
public int getBackgroundColor(TaskInfo taskInfo) {
synchronized (mTaskBackgroundColors) {
@@ -256,7 +257,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private static class IStartingWindowImpl extends IStartingWindow.Stub
+ implements ExternalInterfaceBinder {
private StartingWindowController mController;
private SingleInstanceRemoteListener<StartingWindowController,
IStartingWindowListener> mListener;
@@ -276,7 +278,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 5799394..fdf073f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -23,23 +23,28 @@
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
/**
* Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+ new ArrayMap<>();
+ // References to the existing interfaces, to be invalidated when they are recreated
+ private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
private Configuration mLastConfiguration;
@@ -67,6 +77,11 @@
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/**
@@ -124,6 +139,47 @@
mUserChangeListeners.remove(listener);
}
+ /**
+ * Adds an interface that can be called from a remote process. This method takes a supplier
+ * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+ * will request new binder instances for each instance of Launcher that it provides binders
+ * to.
+ *
+ * @param extra the key for the interface, {@see ShellSharedConstants}
+ * @param binderSupplier the supplier of the binder to pass to the external process
+ * @param callerInstance the instance of the caller, purely for logging
+ */
+ public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+ Object callerInstance) {
+ ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+ callerInstance.getClass().getSimpleName(), extra);
+ if (mExternalInterfaceSuppliers.containsKey(extra)) {
+ throw new IllegalArgumentException("Supplier with same key already exists: "
+ + extra);
+ }
+ mExternalInterfaceSuppliers.put(extra, binderSupplier);
+ }
+
+ /**
+ * Updates the given bundle with the set of external interfaces, invalidating the old set of
+ * binders.
+ */
+ private void createExternalInterfaces(Bundle output) {
+ // Invalidate the old binders
+ for (int i = 0; i < mExternalInterfaces.size(); i++) {
+ mExternalInterfaces.valueAt(i).invalidate();
+ }
+ mExternalInterfaces.clear();
+
+ // Create new binders for each key
+ for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+ final String key = mExternalInterfaceSuppliers.keyAt(i);
+ final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+ mExternalInterfaces.put(key, b);
+ output.putBinder(key, b.asBinder());
+ }
+ }
+
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +260,14 @@
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+ if (!mExternalInterfaces.isEmpty()) {
+ pw.println(innerPrefix + "mExternalInterfaces={");
+ for (String key : mExternalInterfaces.keySet()) {
+ pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+ }
+ pw.println(innerPrefix + "}");
+ }
}
/**
@@ -211,7 +275,6 @@
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
-
@Override
public void onInit() {
try {
@@ -222,28 +285,6 @@
}
@Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = mShellCommandHandler.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +315,38 @@
mMainExecutor.execute(() ->
ShellController.this.onUserProfilesChanged(profiles));
}
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void createExternalInterfaces(Bundle bundle) {
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ ShellController.this.createExternalInterfaces(bundle);
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to get Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 2108c82..bc5dd11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -37,18 +38,6 @@
default void onInit() {}
/**
- * Dumps the shell state.
- */
- default void dump(PrintWriter pw) {}
-
- /**
- * Handles a shell command.
- */
- default boolean handleCommand(final String[] args, PrintWriter pw) {
- return false;
- }
-
- /**
* Notifies the Shell that the configuration has changed.
*/
default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@
* Notifies the Shell when a profile belonging to the user changes.
*/
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
+ * Updates the given {@param bundle} with the set of exposed interfaces.
+ */
+ default void createExternalInterfaces(Bundle bundle) {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
new file mode 100644
index 0000000..bdda6a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+ // See IPip.aidl
+ public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See ISplitScreen.aidl
+ public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+ // See IOneHanded.aidl
+ public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+ // See IShellTransitions.aidl
+ public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+ "extra_shell_shell_transitions";
+ // See IStartingWindow.aidl
+ public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+ "extra_shell_starting_window";
+ // See IRecentTasks.aidl
+ public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
+ // See IBackAnimation.aidl
+ public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+ // See IFloatingTasks.aidl
+ public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
+ // See IDesktopMode.aidl
+ public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d..da39017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@
*/
@ExternalThread
public interface ShellTransitions {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate remote transitions.
- */
- default IShellTransitions createExternalInterface() {
- return null;
- }
-
/**
* Registers a remote transition.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 29d25bc..d1bc738 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -25,10 +25,12 @@
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,11 +63,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -115,6 +119,7 @@
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -142,6 +147,7 @@
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@NonNull DisplayController displayController,
@@ -156,10 +162,14 @@
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellController = shellController;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
+
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
@@ -193,6 +203,10 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IShellTransitionsImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -441,31 +455,34 @@
return;
}
- // apply transfer starting window directly if there is no other task change. Since this
- // is an activity->activity situation, we can detect it by selecting transitions with only
- // 2 changes where neither are tasks and one is a starting-window recipient.
final int changeSize = info.getChanges().size();
- if (changeSize == 2) {
- boolean nonTaskChange = true;
- boolean transferStartingWindow = false;
- for (int i = changeSize - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null) {
- nonTaskChange = false;
- break;
- }
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- transferStartingWindow = true;
- }
+ boolean taskChange = false;
+ boolean transferStartingWindow = false;
+ boolean allOccluded = changeSize > 0;
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ taskChange |= change.getTaskInfo() != null;
+ transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+ if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
+ allOccluded = false;
}
- if (nonTaskChange && transferStartingWindow) {
- t.apply();
- finishT.apply();
- // Treat this as an abort since we are bypassing any merge logic and effectively
- // finishing immediately.
- onAbort(transitionToken);
- return;
- }
+ }
+ // There does not need animation when:
+ // A. Transfer starting window. Apply transfer starting window directly if there is no other
+ // task change. Since this is an activity->activity situation, we can detect it by selecting
+ // transitions with only 2 changes where neither are tasks and one is a starting-window
+ // recipient.
+ if (!taskChange && transferStartingWindow && changeSize == 2
+ // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
+ // changes are underneath another change.
+ || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
+ && allOccluded)) {
+ t.apply();
+ finishT.apply();
+ // Treat this as an abort since we are bypassing any merge logic and effectively
+ // finishing immediately.
+ onAbort(transitionToken);
+ return;
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -542,6 +559,22 @@
"This shouldn't happen, maybe the default handler is broken.");
}
+ /**
+ * Gives every handler (in order) a chance to handle request until one consumes the transition.
+ * @return the WindowContainerTransaction given by the handler which consumed the transition.
+ */
+ public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+ for (int i = mHandlers.size() - 1; i >= 0; --i) {
+ if (mHandlers.get(i) == skip) continue;
+ WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
+ if (wct != null) {
+ return wct;
+ }
+ }
+ return null;
+ }
+
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
@@ -716,8 +749,8 @@
null /* newDisplayAreaInfo */);
}
}
- active.mToken = mOrganizer.startTransition(
- request.getType(), transitionToken, wct);
+ mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
+ active.mToken = transitionToken;
mActiveTransitions.add(active);
}
@@ -726,7 +759,7 @@
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
- active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
+ active.mToken = mOrganizer.startNewTransition(type, wct);
mActiveTransitions.add(active);
return active.mToken;
}
@@ -897,17 +930,6 @@
*/
@ExternalThread
private class ShellTransitionImpl implements ShellTransitions {
- private IShellTransitionsImpl mIShellTransitions;
-
- @Override
- public IShellTransitions createExternalInterface() {
- if (mIShellTransitions != null) {
- mIShellTransitions.invalidate();
- }
- mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
- return mIShellTransitions;
- }
-
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull RemoteTransition remoteTransition) {
@@ -928,7 +950,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub
+ implements ExternalInterfaceBinder {
private Transitions mTransitions;
IShellTransitionsImpl(Transitions transitions) {
@@ -938,7 +961,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mTransitions = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9e49b51..3df33f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,6 +26,7 @@
import android.app.ActivityTaskManager;
import android.content.Context;
import android.os.Handler;
+import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -46,7 +47,7 @@
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
*/
-public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -57,6 +58,8 @@
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
+ private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
@@ -81,12 +84,12 @@
}
@Override
- public CaptionWindowDecoration createWindowDecoration(
+ public boolean createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- if (!shouldShowWindowDecor(taskInfo)) return null;
+ if (!shouldShowWindowDecor(taskInfo)) return false;
final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
mContext,
mDisplayController,
@@ -96,30 +99,24 @@
mMainHandler,
mMainChoreographer,
mSyncQueue);
+ mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
- setupWindowDecorationForTransition(taskInfo, startT, finishT, windowDecoration);
+ setupWindowDecorationForTransition(taskInfo, startT, finishT);
setupCaptionColor(taskInfo, windowDecoration);
- return windowDecoration;
+ return true;
}
@Override
- public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
- if (!(windowDecor instanceof CaptionWindowDecoration)) return null;
- final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor;
- if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) {
- return null;
- }
- return captionWindowDecor;
- }
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
- @Override
- public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
decoration.relayout(taskInfo);
-
setupCaptionColor(taskInfo, decoration);
}
@@ -132,11 +129,22 @@
public void setupWindowDecorationForTransition(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- CaptionWindowDecoration decoration) {
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
+
decoration.relayout(taskInfo, startT, finishT);
}
+ @Override
+ public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ final CaptionWindowDecoration decoration =
+ mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ if (decoration == null) return;
+
+ decoration.close();
+ }
+
private class CaptionTouchEventListener implements
View.OnClickListener, View.OnTouchListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d9697d2..d7f71c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -19,8 +19,6 @@
import android.app.ActivityManager;
import android.view.SurfaceControl;
-import androidx.annotation.Nullable;
-
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
/**
@@ -28,10 +26,8 @@
* customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
* interactions with UI widgets in window decorations and send corresponding requests to system
* servers.
- *
- * @param <T> The actual decoration type
*/
-public interface WindowDecorViewModel<T extends AutoCloseable> {
+public interface WindowDecorViewModel {
/**
* Sets the transition starter that starts freeform task transitions.
@@ -50,29 +46,19 @@
* @param finishT the finish transaction to restore states after the transition
* @return the window decoration object
*/
- @Nullable T createWindowDecoration(
+ boolean createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
/**
- * Adopts the window decoration if possible.
- * May be {@code null} if a window decor is not needed or the given one is incompatible.
- *
- * @param windowDecor the potential window decoration to adopt
- * @return the window decoration if it can be adopted, or {@code null} otherwise.
- */
- @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
-
- /**
* Notifies a task info update on the given task, with the window decoration created previously
* for this task by {@link #createWindowDecoration}.
*
* @param taskInfo the new task info of the task
- * @param windowDecoration the window decoration created for the task
*/
- void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+ void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo);
/**
* Notifies a transition is about to start about the given task to give the window decoration a
@@ -80,11 +66,16 @@
*
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
- * @param windowDecoration the window decoration created for the task
*/
void setupWindowDecorationForTransition(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- T windowDecoration);
+ SurfaceControl.Transaction finishT);
+
+ /**
+ * Destroys the window decoration of the give task.
+ *
+ * @param taskInfo the info of the task
+ */
+ void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90a3773..077e9ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,7 +63,9 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Ignore;
@@ -102,6 +104,9 @@
@Mock
private IBackNaviAnimationController mIBackNaviAnimationController;
+ @Mock
+ private ShellController mShellController;
+
private BackAnimationController mController;
private int mEventTime = 0;
@@ -118,7 +123,7 @@
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit,
+ mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
@@ -175,6 +180,12 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -250,7 +261,7 @@
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit,
+ mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dd23d97..c850a3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -68,6 +69,8 @@
public class DesktopModeControllerTest extends ShellTestCase {
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellTaskOrganizer mShellTaskOrganizer;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -94,8 +97,8 @@
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootTaskDisplayAreaOrganizer, mMockTransitions,
+ mController = new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
mDesktopModeTaskRepository, mMockHandler, mExecutor);
when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
index a88c837..d378a17 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.After;
import org.junit.Before;
@@ -168,6 +169,18 @@
}
}
+ @Test
+ public void onInit_addExternalInterface() {
+ if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+ createController();
+ setUpTabletConfig();
+ mController.onInit();
+
+ verify(mShellController, times(1)).addExternalInterface(
+ ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
+ }
+ }
+
//
// Tests for floating layer, which is only available for tablets.
//
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 0fd5cb0..7068a84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -17,17 +17,10 @@
package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_MAXIMIZE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE;
-
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
@@ -44,9 +37,9 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
import org.junit.Test;
@@ -65,9 +58,7 @@
@Mock
private Transitions mTransitions;
@Mock
- private FullscreenTaskListener<?> mFullscreenTaskListener;
- @Mock
- private FreeformTaskListener<?> mFreeformTaskListener;
+ private WindowDecorViewModel mWindowDecorViewModel;
private FreeformTaskTransitionObserver mTransitionObserver;
@@ -82,7 +73,7 @@
doReturn(pm).when(context).getPackageManager();
mTransitionObserver = new FreeformTaskTransitionObserver(
- context, mShellInit, mTransitions, mFullscreenTaskListener, mFreeformTaskListener);
+ context, mShellInit, mTransitions, mWindowDecorViewModel);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
Runnable.class);
@@ -112,11 +103,12 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mFreeformTaskListener).createWindowDecoration(change, startT, finishT);
+ verify(mWindowDecorViewModel).createWindowDecoration(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Test
- public void testObtainsWindowDecorOnCloseTransition_freeform() {
+ public void testPreparesWindowDecorOnCloseTransition_freeform() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
@@ -128,7 +120,8 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
+ verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+ change.getTaskInfo(), startT, finishT);
}
@Test
@@ -138,17 +131,13 @@
final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
info.addChange(change);
- final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
-
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(windowDecor, never()).close();
+ verify(mWindowDecorViewModel, never()).destroyWindowDecoration(change.getTaskInfo());
}
@Test
@@ -159,8 +148,6 @@
info.addChange(change);
final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -169,7 +156,7 @@
mTransitionObserver.onTransitionStarting(transition);
mTransitionObserver.onTransitionFinished(transition, false);
- verify(windowDecor).close();
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change.getTaskInfo());
}
@Test
@@ -192,10 +179,6 @@
final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
info2.addChange(change2);
- final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
- doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change2.getTaskInfo()), any(), any());
-
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -204,7 +187,7 @@
mTransitionObserver.onTransitionFinished(transition1, false);
- verify(windowDecor2).close();
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
@Test
@@ -215,10 +198,6 @@
final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0);
info1.addChange(change1);
- final AutoCloseable windowDecor1 = mock(AutoCloseable.class);
- doReturn(windowDecor1).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change1.getTaskInfo()), any(), any());
-
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
@@ -231,10 +210,6 @@
final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
info2.addChange(change2);
- final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
- doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change2.getTaskInfo()), any(), any());
-
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -243,48 +218,8 @@
mTransitionObserver.onTransitionFinished(transition1, false);
- verify(windowDecor1).close();
- verify(windowDecor2).close();
- }
-
- @Test
- public void testTransfersWindowDecorOnMaximize() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN);
- final TransitionInfo info = new TransitionInfo(TRANSIT_MAXIMIZE, 0);
- info.addChange(change);
-
- final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
-
- final IBinder transition = mock(IBinder.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- mTransitionObserver.onTransitionStarting(transition);
-
- verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
- verify(mFullscreenTaskListener).adoptWindowDecoration(
- eq(change), same(startT), same(finishT), any());
- }
-
- @Test
- public void testTransfersWindowDecorOnRestoreFromMaximize() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_RESTORE_FROM_MAXIMIZE, 0);
- info.addChange(change);
-
- final IBinder transition = mock(IBinder.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- mTransitionObserver.onTransitionStarting(transition);
-
- verify(mFullscreenTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
- verify(mFreeformTaskListener).adoptWindowDecoration(
- eq(change), same(startT), same(finishT), any());
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change1.getTaskInfo());
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index cf8297e..8ad3d2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -176,6 +177,12 @@
}
@Test
+ public void testControllerRegisteresExternalInterface() {
+ verify(mMockShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 1e08f1e..d06fb55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -61,6 +62,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -152,6 +154,12 @@
}
@Test
+ public void instantiatePipController_registerExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerUserChangeListener() {
verify(mShellController, times(1)).addUserChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b8aaaa7..f6ac3ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -57,7 +58,9 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -84,6 +87,8 @@
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@@ -101,7 +106,7 @@
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -121,6 +126,12 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ }
+
+ @Test
public void testAddRemoveSplitNotifyChange() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5a68361..55883ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -133,6 +134,15 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+ }
+
+ @Test
public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3..90165d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -36,7 +37,9 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -56,25 +59,34 @@
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
- private @Mock ShellInit mShellInit;
+ private @Mock ShellController mShellController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
private @Mock IconProvider mIconProvider;
private @Mock TransactionPool mTransactionPool;
private StartingWindowController mController;
+ private ShellInit mShellInit;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
- mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
- mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mController = new StartingWindowController(mContext, mShellInit, mShellController,
+ mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit.init();
}
@Test
- public void instantiate_addInitCallback() {
+ public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
+
+ @Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index d6ddba9..fbc50c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.sysui;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -30,6 +34,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
@@ -49,6 +54,7 @@
public class ShellControllerTest extends ShellTestCase {
private static final int TEST_USER_ID = 100;
+ private static final String EXTRA_TEST_BINDER = "test_binder";
@Mock
private ShellInit mShellInit;
@@ -81,6 +87,47 @@
}
@Test
+ public void testAddExternalInterface_ensureCallback() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+ Bundle b = new Bundle();
+ mController.asShell().createExternalInterfaces(b);
+ assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+ }
+
+ @Test
+ public void testAddExternalInterface_disallowDuplicateKeys() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ assertThrows(IllegalArgumentException.class, () -> {
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ });
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index c6492be..c764741 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -45,7 +45,6 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -67,10 +66,12 @@
import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.IWindowContainerToken;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
@@ -86,7 +87,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -117,18 +120,31 @@
@Before
public void setUp() {
doAnswer(invocation -> invocation.getArguments()[1])
- .when(mOrganizer).startTransition(anyInt(), any(), any());
+ .when(mOrganizer).startTransition(any(), any());
}
@Test
public void instantiate_addInitCallback() {
ShellInit shellInit = mock(ShellInit.class);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
verify(shellInit, times(1)).addInitCallback(any(), eq(t));
}
@Test
+ public void instantiateController_addExternalInterface() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ ShellController shellController = mock(ShellController.class);
+ final Transitions t = new Transitions(mContext, shellInit, shellController,
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ verify(shellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -136,7 +152,7 @@
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -188,7 +204,7 @@
// Make a request that will be rejected by the testhandler.
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
@@ -199,10 +215,12 @@
// Make a request that will be handled by testhandler but not animated by it.
RunningTaskInfo mwTaskInfo =
createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ // Make the wct non-empty.
+ handlerWCT.setFocusable(new WindowContainerToken(mock(IWindowContainerToken.class)), true);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
verify(mOrganizer, times(1)).startTransition(
- eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+ eq(transitToken), eq(handlerWCT));
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
@@ -217,8 +235,8 @@
transitions.addHandler(topHandler);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(
- eq(TRANSIT_CHANGE), eq(transitToken), eq(handlerWCT));
+ verify(mOrganizer, times(2)).startTransition(
+ eq(transitToken), eq(handlerWCT));
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(TRANSIT_CHANGE).build();
transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
@@ -256,7 +274,7 @@
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
new RemoteTransition(testRemote)));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -406,7 +424,7 @@
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -1060,8 +1078,9 @@
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
shellInit.init();
return t;
}
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index 0a5afe4..db54ae3 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,9 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="24dp"
- android:tint="@android:color/system_accent1_600"/>
+ android:tint="@android:color/system_accent1_600"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
<TextView
android:id="@android:id/text1"
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 54916a2..a3d71b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -30,7 +30,8 @@
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
- android:contentDescription="Permission Icon"/>
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index fc5ff08..5c9ab7b 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -103,7 +103,7 @@
private final Runnable mTimeoutRunnable = this::timeout;
- private boolean mStopAfterFirstMatch;;
+ private boolean mStopAfterFirstMatch;
/**
* A state enum for devices' discovery.
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index df6f08d..2f5b5f4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -294,5 +294,6 @@
dxflags: ["--multi-dex"],
required: [
"privapp_whitelist_com.android.systemui",
+ "wmshell.protolog.json.gz",
],
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index dc2c6356..1b7e26b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -361,13 +361,17 @@
*
* The end state of the animation is controlled by [destination]. This value can be any of
* the four corners, any of the four edges, or the center of the view.
+ *
+ * @param onAnimationEnd an optional runnable that will be run once the animation finishes
+ * successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateRemoval(
rootView: View,
destination: Hotspot = Hotspot.CENTER,
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ onAnimationEnd: Runnable? = null,
): Boolean {
if (
!occupiesSpace(
@@ -391,13 +395,28 @@
addListener(child, listener, recursive = false)
}
- // Remove the view so that a layout update is triggered for the siblings and they
- // animate to their next position while the view's removal is also animating.
- parent.removeView(rootView)
- // By adding the view to the overlay, we can animate it while it isn't part of the view
- // hierarchy. It is correctly positioned because we have its previous bounds, and we set
- // them manually during the animation.
- parent.overlay.add(rootView)
+ val viewHasSiblings = parent.childCount > 1
+ if (viewHasSiblings) {
+ // Remove the view so that a layout update is triggered for the siblings and they
+ // animate to their next position while the view's removal is also animating.
+ parent.removeView(rootView)
+ // By adding the view to the overlay, we can animate it while it isn't part of the
+ // view hierarchy. It is correctly positioned because we have its previous bounds,
+ // and we set them manually during the animation.
+ parent.overlay.add(rootView)
+ }
+ // If this view has no siblings, the parent view may shrink to (0,0) size and mess
+ // up the animation if we immediately remove the view. So instead, we just leave the
+ // view in the real hierarchy until the animation finishes.
+
+ val endRunnable = Runnable {
+ if (viewHasSiblings) {
+ parent.overlay.remove(rootView)
+ } else {
+ parent.removeView(rootView)
+ }
+ onAnimationEnd?.run()
+ }
val startValues =
mapOf(
@@ -430,7 +449,8 @@
endValues,
interpolator,
duration,
- ephemeral = true
+ ephemeral = true,
+ endRunnable,
)
if (rootView is ViewGroup) {
@@ -463,7 +483,6 @@
.alpha(0f)
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
.start()
}
}
@@ -477,7 +496,6 @@
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
.setStartDelay(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
.start()
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
new file mode 100644
index 0000000..4eb7c7d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+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 com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/** Detects usage of Context.getSystemService() and suggests to use an injected instance instead. */
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("getSystemService")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ val evaluator = context.evaluator
+ if (
+ !evaluator.isStatic(method) &&
+ method.name == "getSystemService" &&
+ method.containingClass?.qualifiedName == "android.content.Context"
+ ) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Use @Inject to get the handle to a system-level services instead of using " +
+ "Context.getSystemService()"
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "NonInjectedService",
+ briefDescription =
+ "System-level services should be retrieved using " +
+ "@Inject instead of Context.getSystemService().",
+ explanation =
+ "Context.getSystemService() should be avoided because it makes testing " +
+ "difficult. Instead, use an injected service. For example, " +
+ "instead of calling Context.getSystemService(UserManager.class), " +
+ "use @Inject and add UserManager to the constructor",
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(NonInjectedServiceDetector::class.java, Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index b72d03d..eb71d32 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -27,6 +27,7 @@
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
+@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 4879883..312810b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -35,6 +35,7 @@
GetMainLooperViaContextDetector.ISSUE,
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
+ NonInjectedServiceDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
new file mode 100644
index 0000000..26bd8d0
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+
+/*
+ * This file contains stubs of framework APIs and System UI classes for testing purposes only. The
+ * stubs are not used in the lint detectors themselves.
+ */
+@Suppress("UnstableApiUsage")
+internal val androidStubs =
+ arrayOf(
+ java(
+ """
+package android.app;
+
+public class ActivityManager {
+ public static int getCurrentUser() {}
+}
+"""
+ ),
+ java(
+ """
+package android.os;
+import android.content.pm.UserInfo;
+import android.annotation.UserIdInt;
+
+public class UserManager {
+ public UserInfo getUserInfo(@UserIdInt int userId) {}
+}
+"""
+ ),
+ java("""
+package android.annotation;
+
+public @interface UserIdInt {}
+"""),
+ java("""
+package android.content.pm;
+
+public class UserInfo {}
+"""),
+ java("""
+package android.os;
+
+public class Looper {}
+"""),
+ java("""
+package android.os;
+
+public class Handler {}
+"""),
+ java("""
+package android.content;
+
+public class ServiceConnection {}
+"""),
+ java("""
+package android.os;
+
+public enum UserHandle {
+ ALL
+}
+"""),
+ java(
+ """
+package android.content;
+import android.os.UserHandle;
+import android.os.Handler;
+import android.os.Looper;
+import java.util.concurrent.Executor;
+
+public class Context {
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
+ public void registerReceiverAsUser(
+ BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {}
+ public void registerReceiverForAllUsers(
+ BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
+ Handler scheduler) {}
+ public void sendBroadcast(Intent intent) {}
+ public void sendBroadcast(Intent intent, String receiverPermission) {}
+ public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
+ public void bindService(Intent intent) {}
+ public void bindServiceAsUser(
+ Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
+ public void unbindService(ServiceConnection connection) {}
+ public Looper getMainLooper() { return null; }
+ public Executor getMainExecutor() { return null; }
+ public Handler getMainThreadHandler() { return null; }
+ public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
+ public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
+}
+"""
+ ),
+ java(
+ """
+package android.app;
+import android.content.Context;
+
+public class Activity extends Context {}
+"""
+ ),
+ java(
+ """
+package android.graphics;
+
+public class Bitmap {
+ public enum Config {
+ ARGB_8888,
+ RGB_565,
+ HARDWARE
+ }
+ public static Bitmap createBitmap(int width, int height, Config config) {
+ return null;
+ }
+}
+"""
+ ),
+ java("""
+package android.content;
+
+public class BroadcastReceiver {}
+"""),
+ java("""
+package android.content;
+
+public class IntentFilter {}
+"""),
+ java(
+ """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+
+public interface UserTracker {
+ int getUserId();
+ UserInfo getUserInfo();
+}
+"""
+ ),
+ )
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
similarity index 61%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
index bf685f7..564afcb 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class BindServiceViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = BindServiceViaContextDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(
- BindServiceViaContextDetector.ISSUE)
+ override fun getIssues(): List<Issue> = listOf(BindServiceViaContextDetector.ISSUE)
private val explanation = "Binding or unbinding services are synchronous calls"
@Test
fun testBindService() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -49,17 +49,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testBindServiceAsUser() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -73,17 +76,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testUnbindService() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -96,45 +102,15 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.UserHandle;
-
- public class Context {
- public void bindService(Intent intent) {};
- public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
- UserHandle userHandle) {};
- public void unbindService(ServiceConnection connection) {};
- }
- """
- )
-
- private val serviceConnectionStub: TestFile = java(
- """
- package android.content;
-
- public class ServiceConnection {}
- """
- )
-
- private val userHandleStub: TestFile = java(
- """
- package android.os;
-
- public enum UserHandle {
- ALL
- }
- """
- )
-
- private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
similarity index 62%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index da010212f2..06aee8e 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -1,26 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(
- BroadcastSentViaContextDetector.ISSUE)
+ override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
@Test
fun testSendBroadcast() {
- lint().files(
- TestFiles.java(
- """
+ println(stubs.size)
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.content.Context;
@@ -31,21 +48,25 @@
}
}
"""
- ).indented(),
- *stubs)
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectWarningCount(1)
.expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+ "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+ "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ )
}
@Test
fun testSendBroadcastAsUser() {
- lint().files(
- TestFiles.java(
- """
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.content.Context;
import android.os.UserHandle;
@@ -56,21 +77,26 @@
context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
}
}
- """).indented(),
- *stubs)
+ """
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectWarningCount(1)
.expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+ "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+ "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ )
}
@Test
fun testSendBroadcastInActivity() {
- lint().files(
- TestFiles.java(
- """
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.app.Activity;
import android.os.UserHandle;
@@ -82,21 +108,26 @@
}
}
- """).indented(),
- *stubs)
+ """
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectWarningCount(1)
.expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+ "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+ "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ )
}
@Test
fun testNoopIfNoCall() {
- lint().files(
- TestFiles.java(
- """
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.content.Context;
@@ -106,45 +137,15 @@
context.startActivity(intent);
}
}
- """).indented(),
- *stubs)
+ """
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectClean()
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.UserHandle;
-
- public class Context {
- public void sendBroadcast(Intent intent) {};
- public void sendBroadcast(Intent intent, String receiverPermission) {};
- public void sendBroadcastAsUser(Intent intent, UserHandle userHandle,
- String permission) {};
- }
- """
- )
-
- private val activityStub: TestFile = java(
- """
- package android.app;
- import android.content.Context;
-
- public class Activity extends Context {}
- """
- )
-
- private val userHandleStub: TestFile = java(
- """
- package android.os;
-
- public enum UserHandle {
- ALL
- }
- """
- )
-
- private val stubs = arrayOf(contextStub, activityStub, userHandleStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
similarity index 64%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
index ec761cd..c55f399 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -17,13 +17,13 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = GetMainLooperViaContextDetector()
@@ -35,7 +35,8 @@
@Test
fun testGetMainThreadHandler() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -48,17 +49,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(GetMainLooperViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testGetMainLooper() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -71,17 +75,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(GetMainLooperViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testGetMainExecutor() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -94,42 +101,15 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(GetMainLooperViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
-
- public class Context {
- public Looper getMainLooper() { return null; };
- public Executor getMainExecutor() { return null; };
- public Handler getMainThreadHandler() { return null; };
- }
- """
- )
-
- private val looperStub: TestFile = java(
- """
- package android.os;
-
- public class Looper {}
- """
- )
-
- private val handlerStub: TestFile = java(
- """
- package android.os;
-
- public class Handler {}
- """
- )
-
- private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
new file mode 100644
index 0000000..6b9f88f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = NonInjectedServiceDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+ override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
+
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+
+ public class TestClass1 {
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService("user");
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains("Use @Inject to get the handle")
+ }
+
+ @Test
+ fun testGetServiceWithClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserManager;
+
+ public class TestClass2 {
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService(UserManager.class);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains("Use @Inject to get the handle")
+ }
+
+ private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
similarity index 60%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 76c0519..802ceba 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(
- RegisterReceiverViaContextDetector.ISSUE)
+ override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
@Test
fun testRegisterReceiver() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -51,17 +51,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(RegisterReceiverViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testRegisterReceiverAsUser() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -79,17 +82,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(RegisterReceiverViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testRegisterReceiverForAllUsers() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -107,65 +113,15 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(RegisterReceiverViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.Handler;
- import android.os.UserHandle;
-
- public class Context {
- public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
- int flags) {};
- public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
- IntentFilter filter, String broadcastPermission, Handler scheduler) {};
- public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
- String broadcastPermission, Handler scheduler) {};
- }
- """
- )
-
- private val broadcastReceiverStub: TestFile = java(
- """
- package android.content;
-
- public class BroadcastReceiver {}
- """
- )
-
- private val intentFilterStub: TestFile = java(
- """
- package android.content;
-
- public class IntentFilter {}
- """
- )
-
- private val handlerStub: TestFile = java(
- """
- package android.os;
-
- public class Handler {}
- """
- )
-
- private val userHandleStub: TestFile = java(
- """
- package android.os;
-
- public enum UserHandle {
- ALL
- }
- """
- )
-
- private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
- userHandleStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
similarity index 74%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 2738f04..e265837 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -1,13 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class SlowUserQueryDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -134,61 +150,5 @@
.expectClean()
}
- private val activityManagerStub: TestFile =
- java(
- """
- package android.app;
-
- public class ActivityManager {
- public static int getCurrentUser() {};
- }
- """
- )
-
- private val userManagerStub: TestFile =
- java(
- """
- package android.os;
- import android.content.pm.UserInfo;
- import android.annotation.UserIdInt;
-
- public class UserManager {
- public UserInfo getUserInfo(@UserIdInt int userId) {};
- }
- """
- )
-
- private val userIdIntStub: TestFile =
- java(
- """
- package android.annotation;
-
- public @interface UserIdInt {}
- """
- )
-
- private val userInfoStub: TestFile =
- java(
- """
- package android.content.pm;
-
- public class UserInfo {}
- """
- )
-
- private val userTrackerStub: TestFile =
- java(
- """
- package com.android.systemui.settings;
- import android.content.pm.UserInfo;
-
- public interface UserTracker {
- public int getUserId();
- public UserInfo getUserInfo();
- }
- """
- )
-
- private val stubs =
- arrayOf(activityManagerStub, userManagerStub, userIdIntStub, userInfoStub, userTrackerStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
similarity index 70%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 890f2b8..fd6ab09 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -17,7 +17,6 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
@@ -36,7 +35,8 @@
@Test
fun testSoftwareBitmap() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
import android.graphics.Bitmap;
@@ -48,17 +48,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(SoftwareBitmapDetector.ISSUE)
- .run()
- .expectWarningCount(2)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(2)
+ .expectContains(explanation)
}
@Test
fun testHardwareBitmap() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
import android.graphics.Bitmap;
@@ -69,29 +72,14 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(SoftwareBitmapDetector.ISSUE)
- .run()
- .expectWarningCount(0)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(0)
}
- private val bitmapStub: TestFile = java(
- """
- package android.graphics;
-
- public class Bitmap {
- public enum Config {
- ARGB_8888,
- RGB_565,
- HARDWARE
- }
- public static Bitmap createBitmap(int width, int height, Config config) {
- return null;
- }
- }
- """
- )
-
- private val stubs = arrayOf(bitmapStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
index 3175dcf..4d94bab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
@@ -17,8 +17,6 @@
package com.android.systemui.user.ui.compose
-import android.graphics.Bitmap
-import android.graphics.Canvas
import android.graphics.drawable.Drawable
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.Image
@@ -50,10 +48,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -62,6 +58,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.ui.compose.load
import com.android.systemui.compose.SysUiOutlinedButton
import com.android.systemui.compose.SysUiTextButton
@@ -356,10 +353,11 @@
remember(viewModel.iconResourceId) {
val drawable =
checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+ val size = with(density) { 20.dp.toPx() }.toInt()
drawable
.toBitmap(
- size = with(density) { 20.dp.toPx() }.toInt(),
- tintColor = Color.White,
+ width = size,
+ height = size,
)
.asImageBitmap()
}
@@ -392,32 +390,3 @@
),
)
}
-
-/**
- * Converts the [Drawable] to a [Bitmap].
- *
- * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws
- * the `Drawable` onto it. Use sparingly and with care.
- */
-private fun Drawable.toBitmap(
- size: Int? = null,
- tintColor: Color? = null,
-): Bitmap {
- val bitmap =
- if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
- Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- } else {
- Bitmap.createBitmap(
- size ?: intrinsicWidth,
- size ?: intrinsicHeight,
- Bitmap.Config.ARGB_8888
- )
- }
- val canvas = Canvas(bitmap)
- setBounds(0, 0, canvas.width, canvas.height)
- if (tintColor != null) {
- setTint(tintColor.toArgb())
- }
- draw(canvas)
- return bitmap
-}
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
index 6986961..9d063e9 100644
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
@@ -21,11 +21,12 @@
android:paddingEnd="0dp">
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
<corners android:radius="32dp" />
</shape>
</item>
<item
+ android:id="@+id/user_switcher_key_down"
android:drawable="@drawable/ic_ksh_key_down"
android:gravity="end|center_vertical"
android:width="32dp"
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
new file mode 100644
index 0000000..29832a0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto" >
+
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/mobile_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <FrameLayout
+ android:id="@+id/inout_container"
+ android:layout_height="17dp"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical">
+ <ImageView
+ android:id="@+id/mobile_in"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_down"
+ android:visibility="gone"
+ android:paddingEnd="2dp"
+ />
+ <ImageView
+ android:id="@+id/mobile_out"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_up"
+ android:paddingEnd="2dp"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_type"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="2.5dp"
+ android:paddingEnd="1dp"
+ android:visibility="gone" />
+ <Space
+ android:id="@+id/mobile_roaming_space"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/roaming_icon_start_padding"
+ android:visibility="gone"
+ />
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+ <com.android.systemui.statusbar.AnimatedImageView
+ android:id="@+id/mobile_signal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ systemui:hasOverlappingRendering="false"
+ />
+ <ImageView
+ android:id="@+id/mobile_roaming"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_roaming_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming_large"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
new file mode 100644
index 0000000..1b38fd2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/mobile_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical" >
+
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
+
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index a1d1266..b86929e 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -25,7 +25,7 @@
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
- <item name="android:textSize">14dp</item>
+ <item name="android:textSize">14sp</item>
<item name="android:background">@drawable/kg_emergency_button_background</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingLeft">12dp</item>
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index 226bc6a..e474938 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -49,6 +49,8 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
+ android:focusable="false"
+ android:clickable="false"
android:gravity="center"
android:paddingBottom="@*android:dimen/chooser_view_spacing"
android:paddingLeft="24dp"
diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
index a2b3c40..31baf26 100644
--- a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
+++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
@@ -23,8 +23,9 @@
>
<FrameLayout
+ android:id="@+id/media_projection_recent_tasks_container"
android:layout_width="match_parent"
- android:layout_height="256dp">
+ android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_projection_recent_tasks_recycler"
diff --git a/packages/SystemUI/res/layout/media_projection_task_item.xml b/packages/SystemUI/res/layout/media_projection_task_item.xml
index 75f73cd..cfa586f 100644
--- a/packages/SystemUI/res/layout/media_projection_task_item.xml
+++ b/packages/SystemUI/res/layout/media_projection_task_item.xml
@@ -18,7 +18,9 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:clickable="true"
+ >
<ImageView
android:id="@+id/task_icon"
@@ -27,12 +29,12 @@
android:layout_margin="8dp"
android:importantForAccessibility="no" />
- <!-- TODO(b/240924926) use a custom view that will handle thumbnail cropping correctly -->
- <!-- TODO(b/240924926) dynamically change the view size based on the screen size -->
- <ImageView
+ <!-- This view size will be calculated in runtime -->
+ <com.android.systemui.mediaprojection.appselector.view.MediaProjectionTaskView
android:id="@+id/task_thumbnail"
- android:layout_width="100dp"
- android:layout_height="216dp"
- android:scaleType="centerCrop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"
/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index e079fd3..21d12c2 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -29,6 +29,7 @@
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
+ android:background="@drawable/media_ttt_chip_background_receiver"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 10d49b3..d6c63eb 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -18,80 +18,12 @@
-->
<com.android.systemui.statusbar.StatusBarMobileView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/mobile_combo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" >
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/mobile_group"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
- <FrameLayout
- android:id="@+id/inout_container"
- android:layout_height="17dp"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical">
- <ImageView
- android:id="@+id/mobile_in"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_down"
- android:visibility="gone"
- android:paddingEnd="2dp"
- />
- <ImageView
- android:id="@+id/mobile_out"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_up"
- android:paddingEnd="2dp"
- android:visibility="gone"
- />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_type"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingStart="2.5dp"
- android:paddingEnd="1dp"
- android:visibility="gone" />
- <Space
- android:id="@+id/mobile_roaming_space"
- android:layout_height="match_parent"
- android:layout_width="@dimen/roaming_icon_start_padding"
- android:visibility="gone"
- />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical">
- <com.android.systemui.statusbar.AnimatedImageView
- android:id="@+id/mobile_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- systemui:hasOverlappingRendering="false"
- />
- <ImageView
- android:id="@+id/mobile_roaming"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_roaming_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming_large"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
</com.android.systemui.statusbar.StatusBarMobileView>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9d7b01c..49ef330 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -59,4 +59,5 @@
<dimen name="large_dialog_width">348dp</dimen>
<dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 5dcbeb5..599bf30 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,6 +68,7 @@
<dimen name="qs_security_footer_background_inset">0dp</dimen>
<dimen name="qs_panel_padding_top">8dp</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">472dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 37549c9..9188ce0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -337,9 +337,6 @@
have been scrolled off-screen. -->
<bool name="config_showNotificationShelf">true</bool>
- <!-- Whether or not the notifications should always fade as they are dismissed. -->
- <bool name="config_fadeNotificationsOnDismiss">false</bool>
-
<!-- Whether or not the fade on the notification is based on the amount that it has been swiped
off-screen. -->
<bool name="config_fadeDependingOnAmountSwiped">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f7019dc..778dd45 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -559,7 +559,8 @@
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
<dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
- <dimen name="qs_panel_padding_top">80dp</dimen>
+ <dimen name="qs_panel_padding_top">48dp</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">80dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -1056,9 +1057,8 @@
<!-- Media tap-to-transfer chip for receiver device -->
<dimen name="media_ttt_chip_size_receiver">100dp</dimen>
<dimen name="media_ttt_icon_size_receiver">95dp</dimen>
- <!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
- the circular chip. -->
- <dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <!-- Add some padding for the generic icon so it doesn't go all the way to the border. -->
+ <dimen name="media_ttt_generic_icon_padding">12dp</dimen>
<dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
@@ -1457,6 +1457,9 @@
<dimen name="media_projection_app_selector_icon_size">32dp</dimen>
<dimen name="media_projection_app_selector_recents_padding">16dp</dimen>
<dimen name="media_projection_app_selector_loader_size">32dp</dimen>
+ <dimen name="media_projection_app_selector_task_rounded_corners">10dp</dimen>
+ <dimen name="media_projection_app_selector_task_icon_size">24dp</dimen>
+ <dimen name="media_projection_app_selector_task_icon_margin">8dp</dimen>
<!-- Dream overlay related dimensions -->
<dimen name="dream_overlay_status_bar_height">60dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
index ffab3cd..12e0b9a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.animation
import android.view.View
+import android.view.View.LAYOUT_DIRECTION_RTL
import android.view.ViewGroup
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -58,9 +59,15 @@
// progress == 0 -> -translationMax
// progress == 1 -> 0
val xTrans = (progress - 1f) * translationMax
+ val rtlMultiplier =
+ if (rootView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ -1
+ } else {
+ 1
+ }
viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
if (shouldBeAnimated()) {
- view.get()?.translationX = xTrans * direction.multiplier
+ view.get()?.translationX = xTrans * direction.multiplier * rtlMultiplier
}
}
}
@@ -90,7 +97,7 @@
/** Direction of the animation. */
enum class Direction(val multiplier: Float) {
- LEFT(-1f),
- RIGHT(1f),
+ START(-1f),
+ END(1f),
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
new file mode 100644
index 0000000..72f8b7b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -0,0 +1,209 @@
+package com.android.systemui.shared.recents.utilities;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Utility class to position the thumbnail in the TaskView
+ */
+public class PreviewPositionHelper {
+
+ public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
+
+ // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
+ private final RectF mClippedInsets = new RectF();
+ private final Matrix mMatrix = new Matrix();
+ private boolean mIsOrientationChanged;
+
+ public Matrix getMatrix() {
+ return mMatrix;
+ }
+
+ public void setOrientationChanged(boolean orientationChanged) {
+ mIsOrientationChanged = orientationChanged;
+ }
+
+ public boolean isOrientationChanged() {
+ return mIsOrientationChanged;
+ }
+
+ /**
+ * Updates the matrix based on the provided parameters
+ */
+ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
+ int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+ int currentRotation, boolean isRtl) {
+ boolean isRotated = false;
+ boolean isOrientationDifferent;
+
+ int thumbnailRotation = thumbnailData.rotation;
+ int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+ RectF thumbnailClipHint = new RectF();
+ float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+ float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+ thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+
+ float scale = thumbnailData.scale;
+ final float thumbnailScale;
+
+ // Landscape vs portrait change.
+ // Note: Disable rotation in grid layout.
+ boolean windowingModeSupportsRotation =
+ thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet;
+ isOrientationDifferent = isOrientationChange(deltaRotate)
+ && windowingModeSupportsRotation;
+ if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
+ // If we haven't measured , skip the thumbnail drawing and only draw the background
+ // color
+ thumbnailScale = 0f;
+ } else {
+ // Rotate the screenshot if not in multi-window mode
+ isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
+
+ float surfaceWidth = thumbnailBounds.width() / scale;
+ float surfaceHeight = thumbnailBounds.height() / scale;
+ float availableWidth = surfaceWidth
+ - (thumbnailClipHint.left + thumbnailClipHint.right);
+ float availableHeight = surfaceHeight
+ - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+ float canvasAspect = canvasWidth / (float) canvasHeight;
+ float availableAspect = isRotated
+ ? availableHeight / availableWidth
+ : availableWidth / availableHeight;
+ boolean isAspectLargelyDifferent =
+ Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+ availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
+ if (isRotated && isAspectLargelyDifferent) {
+ // Do not rotate thumbnail if it would not improve fit
+ isRotated = false;
+ isOrientationDifferent = false;
+ }
+
+ if (isAspectLargelyDifferent) {
+ // Crop letterbox insets if insets isn't already clipped
+ thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
+ thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
+ thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
+ thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
+ availableWidth = surfaceWidth
+ - (thumbnailClipHint.left + thumbnailClipHint.right);
+ availableHeight = surfaceHeight
+ - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+ }
+
+ final float targetW, targetH;
+ if (isOrientationDifferent) {
+ targetW = canvasHeight;
+ targetH = canvasWidth;
+ } else {
+ targetW = canvasWidth;
+ targetH = canvasHeight;
+ }
+ float targetAspect = targetW / targetH;
+
+ // Update the clipHint such that
+ // > the final clipped position has same aspect ratio as requested by canvas
+ // > first fit the width and crop the extra height
+ // > if that will leave empty space, fit the height and crop the width instead
+ float croppedWidth = availableWidth;
+ float croppedHeight = croppedWidth / targetAspect;
+ if (croppedHeight > availableHeight) {
+ croppedHeight = availableHeight;
+ if (croppedHeight < targetH) {
+ croppedHeight = Math.min(targetH, surfaceHeight);
+ }
+ croppedWidth = croppedHeight * targetAspect;
+
+ // One last check in case the task aspect radio messed up something
+ if (croppedWidth > surfaceWidth) {
+ croppedWidth = surfaceWidth;
+ croppedHeight = croppedWidth / targetAspect;
+ }
+ }
+
+ // Update the clip hints. Align to 0,0, crop the remaining.
+ if (isRtl) {
+ thumbnailClipHint.left += availableWidth - croppedWidth;
+ if (thumbnailClipHint.right < 0) {
+ thumbnailClipHint.left += thumbnailClipHint.right;
+ thumbnailClipHint.right = 0;
+ }
+ } else {
+ thumbnailClipHint.right += availableWidth - croppedWidth;
+ if (thumbnailClipHint.left < 0) {
+ thumbnailClipHint.right += thumbnailClipHint.left;
+ thumbnailClipHint.left = 0;
+ }
+ }
+ thumbnailClipHint.bottom += availableHeight - croppedHeight;
+ if (thumbnailClipHint.top < 0) {
+ thumbnailClipHint.bottom += thumbnailClipHint.top;
+ thumbnailClipHint.top = 0;
+ } else if (thumbnailClipHint.bottom < 0) {
+ thumbnailClipHint.top += thumbnailClipHint.bottom;
+ thumbnailClipHint.bottom = 0;
+ }
+
+ thumbnailScale = targetW / (croppedWidth * scale);
+ }
+
+ if (!isRotated) {
+ mMatrix.setTranslate(
+ -thumbnailClipHint.left * scale,
+ -thumbnailClipHint.top * scale);
+ } else {
+ setThumbnailRotation(deltaRotate, thumbnailBounds);
+ }
+
+ mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
+
+ mMatrix.postScale(thumbnailScale, thumbnailScale);
+ mIsOrientationChanged = isOrientationDifferent;
+ }
+
+ private int getRotationDelta(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ /**
+ * @param deltaRotation the number of 90 degree turns from the current orientation
+ * @return {@code true} if the change in rotation results in a shift from landscape to
+ * portrait or vice versa, {@code false} otherwise
+ */
+ private boolean isOrientationChange(int deltaRotation) {
+ return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ }
+
+ private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
+ float translateX = 0;
+ float translateY = 0;
+
+ mMatrix.setRotate(90 * deltaRotate);
+ switch (deltaRotate) { /* Counter-clockwise */
+ case Surface.ROTATION_90:
+ translateX = thumbnailPosition.height();
+ break;
+ case Surface.ROTATION_270:
+ translateY = thumbnailPosition.width();
+ break;
+ case Surface.ROTATION_180:
+ translateX = thumbnailPosition.width();
+ translateY = thumbnailPosition.height();
+ break;
+ }
+ mMatrix.postTranslate(translateX, translateY);
+ }
+
+ public RectF getClippedInsets() {
+ return mClippedInsets;
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 56326e3..77a13bd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -62,6 +62,16 @@
return false; // Default
}
+ /**
+ * Compares the ratio of two quantities and returns whether that ratio is greater than the
+ * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+ * of a percentage.
+ */
+ public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+ float bound) {
+ return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+ }
+
/** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
public static float computeContrastBetweenColors(int bg, int fg) {
float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 85278dd..f2742b7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -43,27 +43,8 @@
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
- // See IPip.aidl
- public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
- // See ISplitScreen.aidl
- public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
- // See IFloatingTasks.aidl
- public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
- // See IOneHanded.aidl
- public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
- // See IShellTransitions.aidl
- public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
- "extra_shell_shell_transitions";
- // See IStartingWindow.aidl
- public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
- "extra_shell_starting_window";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
- // See IRecentTasks.aidl
- public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
- public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IDesktopMode.aidl
- public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index b444f4c..fd38661 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -69,7 +69,7 @@
private var isCharging = false
private var dozeAmount = 0f
- private var isKeyguardShowing = false
+ private var isKeyguardVisible = false
private val regionSamplingEnabled =
featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
@@ -145,7 +145,7 @@
private val batteryCallback = object : BatteryStateChangeCallback {
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- if (isKeyguardShowing && !isCharging && charging) {
+ if (isKeyguardVisible && !isCharging && charging) {
clock?.animations?.charge()
}
isCharging = charging
@@ -168,9 +168,9 @@
}
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
- isKeyguardShowing = showing
- if (!isKeyguardShowing) {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ isKeyguardVisible = visible
+ if (!isKeyguardVisible) {
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 453072b..5d86ccd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -176,6 +176,8 @@
@Override
public void startAppearAnimation() {
+ setAlpha(1f);
+ setTranslationY(0);
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 1e5c53d..2cc5ccdc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,7 +24,6 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -107,8 +106,6 @@
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 5b22324..9871645 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,9 +330,6 @@
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..c46e33d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,7 +22,6 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -124,8 +123,6 @@
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2bdb1b8..353c369 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -538,7 +538,7 @@
}
/**
- * Runs after a succsssful authentication only
+ * Runs after a successful authentication only
*/
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
@@ -1063,6 +1063,7 @@
mPopup.dismiss();
mPopup = null;
}
+ setupUserSwitcher();
}
@Override
@@ -1073,6 +1074,11 @@
android.R.attr.textColorPrimary));
header.setBackground(mView.getContext().getDrawable(
R.drawable.bouncer_user_switcher_header_bg));
+ Drawable keyDownDrawable =
+ ((LayerDrawable) header.getBackground().mutate()).findDrawableByLayerId(
+ R.id.user_switcher_key_down);
+ keyDownDrawable.setTintList(Utils.getColorAttr(mView.getContext(),
+ android.R.attr.textColorPrimary));
}
}
@@ -1096,7 +1102,6 @@
return;
}
- mView.setAlpha(1f);
mUserSwitcherViewGroup.setAlpha(0f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
1f);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57058b7..d448f40 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -427,6 +427,7 @@
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
+ mView.setAlpha(1f);
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController().startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 9d0a8ac..ac00e94 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,12 +61,6 @@
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
- * Some auth is required because the trustagent expired either from timeout or manually by
- * the user
- */
- int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
-
- /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c715a4e..e9f06ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -212,9 +212,9 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
- if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (visible) {
+ if (DEBUG) Slog.v(TAG, "refresh statusview visible:true");
refreshTime();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 7d6f377..f974e27 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -20,8 +20,8 @@
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -49,13 +49,14 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
- ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+ ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
ViewIdToTranslate(
- R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
- ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
+ R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+ ViewIdToTranslate(
+ R.id.notification_stack_scroller, END, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.start_button, START, filterNever),
+ ViewIdToTranslate(R.id.end_button, END, filterNever)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f5b27dd..cd9089e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -308,7 +308,8 @@
HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
private int mPhoneState;
- private boolean mKeyguardIsVisible;
+ private boolean mKeyguardShowing;
+ private boolean mKeyguardOccluded;
private boolean mCredentialAttempted;
private boolean mKeyguardGoingAway;
private boolean mGoingToSleep;
@@ -318,7 +319,6 @@
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
private boolean mAssistantVisible;
- private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
private boolean mOccludingAppRequestingFace;
private boolean mSecureCameraLaunched;
@@ -681,14 +681,42 @@
}
/**
- * Updates KeyguardUpdateMonitor's internal state to know if keyguard is occluded
+ * Updates KeyguardUpdateMonitor's internal state to know if keyguard is showing and if
+ * its occluded. The keyguard is considered visible if its showing and NOT occluded.
*/
- public void setKeyguardOccluded(boolean occluded) {
- mKeyguardOccluded = occluded;
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
- }
+ public void setKeyguardShowing(boolean showing, boolean occluded) {
+ final boolean occlusionChanged = mKeyguardOccluded != occluded;
+ final boolean showingChanged = mKeyguardShowing != showing;
+ if (!occlusionChanged && !showingChanged) {
+ return;
+ }
+ final boolean wasKeyguardVisible = isKeyguardVisible();
+ mKeyguardShowing = showing;
+ mKeyguardOccluded = occluded;
+ final boolean isKeyguardVisible = isKeyguardVisible();
+ mLogger.logKeyguardShowingChanged(showing, occluded, isKeyguardVisible);
+
+ if (isKeyguardVisible != wasKeyguardVisible) {
+ if (isKeyguardVisible) {
+ mSecureCameraLaunched = false;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardVisibilityChanged(isKeyguardVisible);
+ }
+ }
+ }
+
+ if (occlusionChanged) {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
+ } else if (showingChanged) {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+ }
+ }
/**
* Request to listen for face authentication when an app is occluding keyguard.
@@ -2442,7 +2470,7 @@
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
- || (mKeyguardIsVisible && !mGoingToSleep
+ || (isKeyguardVisible() && !mGoingToSleep
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
// Gates:
@@ -2518,7 +2546,7 @@
final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
final boolean shouldListenKeyguardState =
- mKeyguardIsVisible
+ isKeyguardVisible()
|| !mDeviceInteractive
|| (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
|| mGoingToSleep
@@ -2567,7 +2595,7 @@
mFingerprintLockedOut,
mGoingToSleep,
mKeyguardGoingAway,
- mKeyguardIsVisible,
+ isKeyguardVisible(),
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
@@ -2589,7 +2617,7 @@
}
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
- final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+ final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -2635,7 +2663,7 @@
// 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 =
- (mBouncerFullyShown && !mGoingToSleep
+ (mBouncerFullyShown
|| mAuthInterruptActive
|| mOccludingAppRequestingFace
|| awakeKeyguard
@@ -2647,6 +2675,7 @@
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& !faceAuthenticated
+ && !mGoingToSleep
&& !fpOrFaceIsLockedOut;
// Aggregate relevant fields for debug logging.
@@ -3145,32 +3174,18 @@
callbacksRefreshCarrierInfo();
}
+ /**
+ * Whether the keyguard is showing and not occluded.
+ */
public boolean isKeyguardVisible() {
- return mKeyguardIsVisible;
+ return isKeyguardShowing() && !mKeyguardOccluded;
}
/**
- * Notifies that the visibility state of Keyguard has changed.
- *
- * <p>Needs to be called from the main thread.
+ * Whether the keyguard is showing. It may still be occluded and not visible.
*/
- public void onKeyguardVisibilityChanged(boolean showing) {
- Assert.isMainThread();
- mLogger.logKeyguardVisibilityChanged(showing);
- mKeyguardIsVisible = showing;
-
- if (showing) {
- mSecureCameraLaunched = false;
- }
-
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onKeyguardVisibilityChangedRaw(showing);
- }
- }
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+ public boolean isKeyguardShowing() {
+ return mKeyguardShowing;
}
/**
@@ -3379,7 +3394,7 @@
callback.onTimeChanged();
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
- callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
+ callback.onKeyguardVisibilityChanged(isKeyguardVisible());
callback.onTelephonyCapable(mTelephonyCapable);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7a42803..bc5ab88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
import android.hardware.biometrics.BiometricSourceType;
-import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
@@ -32,10 +31,6 @@
*/
public class KeyguardUpdateMonitorCallback {
- private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000;
- private long mVisibilityChangedCalled;
- private boolean mShowing;
-
/**
* Called when the battery status changes, e.g. when plugged in or unplugged, charge
* level, etc. changes.
@@ -75,21 +70,6 @@
public void onPhoneStateChanged(int phoneState) { }
/**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- public void onKeyguardVisibilityChanged(boolean showing) { }
-
- public void onKeyguardVisibilityChangedRaw(boolean showing) {
- final long now = SystemClock.elapsedRealtime();
- if (showing == mShowing
- && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return;
- onKeyguardVisibilityChanged(showing);
- mVisibilityChangedCalled = now;
- mShowing = showing;
- }
-
- /**
* Called when the keyguard enters or leaves bouncer mode.
* @param bouncerIsOrWillBeShowing if true, keyguard is showing the bouncer or transitioning
* from/to bouncer mode.
@@ -97,6 +77,12 @@
public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) { }
/**
+ * Called when the keyguard visibility changes.
+ * @param visible whether the keyguard is showing and is NOT occluded
+ */
+ public void onKeyguardVisibilityChanged(boolean visible) { }
+
+ /**
* Called when the keyguard fully transitions to the bouncer or is no longer the bouncer
* @param bouncerIsFullyShowing if true, keyguard is fully showing the bouncer
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2a36676..c41b752 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -447,14 +447,6 @@
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- // reset mIsBouncerShowing state in case it was preemptively set
- // onLongPress
- mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
- updateVisibility();
- }
-
- @Override
public void onKeyguardBouncerStateChanged(boolean bouncer) {
mIsBouncerShowing = bouncer;
updateVisibility();
@@ -507,6 +499,11 @@
// If biometrics were removed, local vars mCanDismissLockScreen and
// mUserUnlockedWithBiometric may not be updated.
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+
+ // reset mIsBouncerShowing state in case it was preemptively set
+ // onLongPress
+ mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
+
updateKeyguardShowing();
if (mIsKeyguardShowing) {
mUserUnlockedWithBiometric =
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
new file mode 100644
index 0000000..50012a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.MessageInitializer
+import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.dagger.KeyguardLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardLog"
+
+/**
+ * Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
+ * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
+ * an overkill.
+ */
+class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+ fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+
+ fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+ fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
+
+ private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
+ buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+ }
+
+ // TODO: remove after b/237743330 is fixed
+ fun logStatusBarCalculatedAlpha(alpha: Float) {
+ debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+ }
+
+ // TODO: remove after b/237743330 is fixed
+ fun logStatusBarExplicitAlpha(alpha: Float) {
+ debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+ }
+
+ // TODO: remove after b/237743330 is fixed
+ fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
+ debugLog(
+ {
+ int1 = visibility
+ double1 = alpha.toDouble()
+ str1 = state
+ },
+ { "changing visibility to $int1 with alpha $double1 in state: $str1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 7a00cd9..54cec71 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -45,7 +45,7 @@
fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
- fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+ fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
@@ -170,8 +170,14 @@
logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
}
- fun logKeyguardVisibilityChanged(showing: Boolean) {
- logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+ fun logKeyguardShowingChanged(showing: Boolean, occluded: Boolean, visible: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = showing
+ bool2 = occluded
+ bool3 = visible
+ }, {
+ "keyguardShowingChanged(showing=$bool1 occluded=$bool2 visible=$bool3)"
+ })
}
fun logMissingSupervisorAppError(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index fe6dbe5..873a695 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -38,6 +38,8 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -66,7 +68,7 @@
private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
- static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+ public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width
// beyond which swipe progress->0
public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
@@ -235,7 +237,11 @@
return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
}
- private float getSwipeAlpha(float progress) {
+ /**
+ * Returns the alpha value depending on the progress of the swipe.
+ */
+ @VisibleForTesting
+ public float getSwipeAlpha(float progress) {
if (mFadeDependingOnAmountSwiped) {
// The more progress has been fade, the lower the alpha value so that the view fades.
return Math.max(1 - progress, 0);
@@ -260,7 +266,7 @@
animView.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
- animView.setAlpha(getSwipeAlpha(swipeProgress));
+ updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
}
}
invalidateGlobalRegion(animView);
@@ -561,6 +567,14 @@
mCallback.onChildSnappedBack(animView, targetLeft);
}
+
+ /**
+ * Called to update the content alpha while the view is swiped
+ */
+ protected void updateSwipeProgressAlpha(View animView, float alpha) {
+ animView.setAlpha(alpha);
+ }
+
/**
* Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
* to tell us what to do
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 11353f6..403941f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -61,8 +61,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mIsKeyguardVisible = showing;
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ mIsKeyguardVisible = visible;
handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8f5cbb7..46c12b2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -875,6 +875,7 @@
static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_SECURE
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DIM_BEHIND;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 4fee083..4363b88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -117,7 +117,7 @@
}
fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
- if (!(keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.isDreaming) ||
+ if (!keyguardStateController.isShowing ||
keyguardUpdateMonitor.userNeedsStrongAuth()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
index ca36375..379c819 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
@@ -52,7 +52,7 @@
private val userId: Int,
private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
private val unregisterAction: BroadcastReceiver.() -> Unit,
- private val bgExecutor: Executor,
+ private val workerExecutor: Executor,
private val logger: BroadcastDispatcherLogger,
private val testPendingRemovalAction: (BroadcastReceiver, Int) -> Boolean
) : BroadcastReceiver(), Dumpable {
@@ -112,7 +112,7 @@
val id = index.getAndIncrement()
logger.logBroadcastReceived(id, userId, intent)
// Immediately return control to ActivityManager
- bgExecutor.execute {
+ workerExecutor.execute {
receiverDatas.forEach {
if (it.filter.matchCategories(intent.categories) == null &&
!testPendingRemovalAction(it.receiver, userId)) {
@@ -138,4 +138,4 @@
println("Categories: ${activeCategories.joinToString(", ")}")
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index eb8cb47..537cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -34,7 +34,8 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.BroadcastRunning
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
@@ -55,7 +56,6 @@
private const val MSG_REMOVE_RECEIVER = 1
private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
private const val TAG = "BroadcastDispatcher"
-private const val DEBUG = true
/**
* SystemUI master Broadcast Dispatcher.
@@ -73,15 +73,16 @@
@SysUISingleton
open class BroadcastDispatcher @Inject constructor(
private val context: Context,
- @Background private val bgLooper: Looper,
- @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ @BroadcastRunning private val broadcastLooper: Looper,
+ @BroadcastRunning private val broadcastExecutor: Executor,
private val dumpManager: DumpManager,
private val logger: BroadcastDispatcherLogger,
private val userTracker: UserTracker,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {
- // Only modify in BG thread
+ // Only modify in BroadcastRunning thread
private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
fun initialize() {
@@ -148,7 +149,7 @@
val data = ReceiverData(
receiver,
filter,
- executor ?: context.mainExecutor,
+ executor ?: mainExecutor,
user ?: context.user,
permission
)
@@ -181,7 +182,7 @@
registerReceiver(
receiver,
filter,
- bgExecutor,
+ broadcastExecutor,
user,
flags,
permission,
@@ -246,8 +247,8 @@
UserBroadcastDispatcher(
context,
userId,
- bgLooper,
- bgExecutor,
+ broadcastLooper,
+ broadcastExecutor,
logger,
removalPendingStore
)
@@ -265,7 +266,7 @@
ipw.decreaseIndent()
}
- private val handler = object : Handler(bgLooper) {
+ private val handler = object : Handler(broadcastLooper) {
override fun handleMessage(msg: Message) {
when (msg.what) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 6b15188..22dc94a 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -16,6 +16,7 @@
package com.android.systemui.broadcast
+import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.os.Handler
@@ -46,8 +47,8 @@
open class UserBroadcastDispatcher(
private val context: Context,
private val userId: Int,
- private val bgLooper: Looper,
- private val bgExecutor: Executor,
+ private val workerLooper: Looper,
+ private val workerExecutor: Executor,
private val logger: BroadcastDispatcherLogger,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {
@@ -66,9 +67,11 @@
val permission: String?
)
- private val bgHandler = Handler(bgLooper)
+ private val wrongThreadErrorMsg = "This method should only be called from the worker thread " +
+ "(which is expected to be the BroadcastRunning thread)"
+ private val workerHandler = Handler(workerLooper)
- // Only modify in BG thread
+ // Only modify in BroadcastRunning thread
@VisibleForTesting
internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
@@ -97,8 +100,7 @@
}
private fun handleRegisterReceiver(receiverData: ReceiverData, flags: Int) {
- Preconditions.checkState(bgLooper.isCurrentThread,
- "This method should only be called from BG thread")
+ Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
receiverToActions
.getOrPut(receiverData.receiver, { ArraySet() })
@@ -113,6 +115,7 @@
logger.logReceiverRegistered(userId, receiverData.receiver, flags)
}
+ @SuppressLint("RegisterReceiverViaContextDetector")
@VisibleForTesting
internal open fun createActionReceiver(
action: String,
@@ -128,7 +131,7 @@
UserHandle.of(userId),
it,
permission,
- bgHandler,
+ workerHandler,
flags
)
logger.logContextReceiverRegistered(userId, flags, it)
@@ -143,15 +146,14 @@
IllegalStateException(e))
}
},
- bgExecutor,
+ workerExecutor,
logger,
removalPendingStore::isPendingRemoval
)
}
private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
- Preconditions.checkState(bgLooper.isCurrentThread,
- "This method should only be called from BG thread")
+ Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
actionsToActionsReceivers.forEach { (key, value) ->
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index c292296..701df89 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -45,6 +45,7 @@
public static final int QS_SWIPE_SIDE = 15;
public static final int BACK_GESTURE = 16;
public static final int QS_SWIPE_NESTED = 17;
+ public static final int MEDIA_SEEKBAR = 18;
@IntDef({
QUICK_SETTINGS,
@@ -65,7 +66,8 @@
LOCK_ICON,
QS_SWIPE_SIDE,
QS_SWIPE_NESTED,
- BACK_GESTURE
+ BACK_GESTURE,
+ MEDIA_SEEKBAR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InteractionType {}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index 5e4f149..f8ee49a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -23,6 +23,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
@@ -153,6 +154,7 @@
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == BRIGHTNESS_SLIDER
+ || interactionType == MEDIA_SEEKBAR
|| interactionType == SHADE_DRAG
|| interactionType == QS_COLLAPSE
|| interactionType == Classifier.UDFPS_AUTHENTICATION
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index 07f94e7..e8c83b1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -18,6 +18,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -119,7 +120,8 @@
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER
- || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE) {
+ || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE
+ || interactionType == MEDIA_SEEKBAR) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index 776bc88..f576a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -20,6 +20,7 @@
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
@@ -93,6 +94,10 @@
case QS_SWIPE_NESTED:
wrongDirection = !vertical;
break;
+ case MEDIA_SEEKBAR:
+ confidence = 0;
+ wrongDirection = vertical;
+ break;
default:
wrongDirection = true;
break;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index de2bdf7..840982c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -22,6 +22,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
import android.graphics.Point;
@@ -91,6 +92,7 @@
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == BRIGHTNESS_SLIDER
+ || interactionType == MEDIA_SEEKBAR
|| interactionType == SHADE_DRAG
|| interactionType == LOCK_ICON) {
return Result.passed(0);
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
deleted file mode 100644
index d6a059d..0000000
--- a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
+++ /dev/null
@@ -1,27 +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.containeddrawable
-
-import android.graphics.drawable.Drawable
-import androidx.annotation.DrawableRes
-
-/** Convenience container for [Drawable] or a way to load it later. */
-sealed class ContainedDrawable {
- data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
- data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 06dbab9..d70b971 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,7 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
-import com.android.systemui.media.dagger.MediaProjectionModule;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.people.PeopleModule;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
new file mode 100644
index 0000000..5f8e540
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BroadcastRunning {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index b598554..4c4aa5c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -33,6 +33,18 @@
boolean isProvisioned();
/**
+ * Whether there's a pulse that's been requested but hasn't started transitioning to pulsing
+ * states yet.
+ */
+ boolean isPulsePending();
+
+ /**
+ * @param isPulsePending whether a pulse has been requested but hasn't started transitioning
+ * to the pulse state yet
+ */
+ void setPulsePending(boolean isPulsePending);
+
+ /**
* Makes a current pulse last for twice as long.
* @param reason why we're extending it.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4161cf6..2e51b51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -280,8 +280,8 @@
/**
* Appends pulse dropped event to logs
*/
- public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
- mLogger.logPulseDropped(pulsePending, state, blocked);
+ public void tracePulseDropped(String from, DozeMachine.State state) {
+ mLogger.logPulseDropped(from, state);
}
/**
@@ -292,6 +292,13 @@
}
/**
+ * Appends pulsing event to logs.
+ */
+ public void tracePulseEvent(String pulseEvent, boolean dozing, int pulseReason) {
+ mLogger.logPulseEvent(pulseEvent, dozing, DozeLog.reasonToString(pulseReason));
+ }
+
+ /**
* Appends pulse dropped event to logs
* @param reason why the pulse was dropped
*/
@@ -424,8 +431,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- traceKeyguard(showing);
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ traceKeyguard(visible);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 4b279ec..cc57662 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -155,11 +155,11 @@
})
}
- fun logKeyguardVisibilityChange(isShowing: Boolean) {
+ fun logKeyguardVisibilityChange(isVisible: Boolean) {
buffer.log(TAG, INFO, {
- bool1 = isShowing
+ bool1 = isVisible
}, {
- "Keyguard visibility change, isShowing=$bool1"
+ "Keyguard visibility change, isVisible=$bool1"
})
}
@@ -224,13 +224,12 @@
})
}
- fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
+ fun logPulseDropped(from: String, state: DozeMachine.State) {
buffer.log(TAG, INFO, {
- bool1 = pulsePending
- str1 = state.name
- bool2 = blocked
+ str1 = from
+ str2 = state.name
}, {
- "Pulse dropped, pulsePending=$bool1 state=$str1 blocked=$bool2"
+ "Pulse dropped, cannot pulse from=$str1 state=$str2"
})
}
@@ -243,6 +242,16 @@
})
}
+ fun logPulseEvent(pulseEvent: String, dozing: Boolean, pulseReason: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = pulseEvent
+ bool1 = dozing
+ str2 = pulseReason
+ }, {
+ "Pulse-$str1 dozing=$bool1 pulseReason=$str2"
+ })
+ }
+
fun logPulseDropped(reason: String) {
buffer.log(TAG, INFO, {
str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00ac8bc..ef454ff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -102,7 +102,6 @@
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
- private boolean mPulsePending;
private Runnable mAodInterruptRunnable;
/** see {@link #onProximityFar} prox for callback */
@@ -303,8 +302,8 @@
null /* onPulseSuppressedListener */);
}
} else {
- proximityCheckThenCall((result) -> {
- if (result != null && result) {
+ proximityCheckThenCall((isNear) -> {
+ if (isNear != null && isNear) {
// In pocket, drop event.
mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
return;
@@ -410,8 +409,8 @@
sWakeDisplaySensorState = wake;
if (wake) {
- proximityCheckThenCall((result) -> {
- if (result != null && result) {
+ proximityCheckThenCall((isNear) -> {
+ if (isNear != null && isNear) {
// In pocket, drop event.
return;
}
@@ -537,24 +536,44 @@
return;
}
- if (mPulsePending || !mAllowPulseTriggers || !canPulse()) {
- if (mAllowPulseTriggers) {
- mDozeLog.tracePulseDropped(mPulsePending, dozeState, mDozeHost.isPulsingBlocked());
+ if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+ if (!mAllowPulseTriggers) {
+ mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
+ } else if (mDozeHost.isPulsePending()) {
+ mDozeLog.tracePulseDropped("requestPulse - pulsePending");
+ } else if (!canPulse()) {
+ mDozeLog.tracePulseDropped("requestPulse", dozeState);
}
runIfNotNull(onPulseSuppressedListener);
return;
}
- mPulsePending = true;
- proximityCheckThenCall((result) -> {
- if (result != null && result) {
+ mDozeHost.setPulsePending(true);
+ proximityCheckThenCall((isNear) -> {
+ if (isNear != null && isNear) {
// in pocket, abort pulse
- mDozeLog.tracePulseDropped("inPocket");
- mPulsePending = false;
+ mDozeLog.tracePulseDropped("requestPulse - inPocket");
+ mDozeHost.setPulsePending(false);
runIfNotNull(onPulseSuppressedListener);
} else {
// not in pocket, continue pulsing
- continuePulseRequest(reason);
+ final boolean isPulsePending = mDozeHost.isPulsePending();
+ mDozeHost.setPulsePending(false);
+ if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+ if (!isPulsePending) {
+ mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
+ + " pending, pulse was cancelled before it could start"
+ + " transitioning to pulsing state.");
+ } else if (mDozeHost.isPulsingBlocked()) {
+ mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
+ } else if (!canPulse()) {
+ mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+ }
+ runIfNotNull(onPulseSuppressedListener);
+ return;
+ }
+
+ mMachine.requestPulse(reason);
}
}, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
@@ -569,16 +588,6 @@
|| mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
}
- private void continuePulseRequest(int reason) {
- mPulsePending = false;
- if (mDozeHost.isPulsingBlocked() || !canPulse()) {
- mDozeLog.tracePulseDropped(mPulsePending, mMachine.getState(),
- mDozeHost.isPulsingBlocked());
- return;
- }
- mMachine.requestPulse(reason);
- }
-
@Nullable
private InstanceId getKeyguardSessionId() {
return mSessionTracker.getSessionId(SESSION_KEYGUARD);
@@ -591,7 +600,7 @@
pw.print(" notificationPulseTime=");
pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
- pw.println(" pulsePending=" + mPulsePending);
+ pw.println(" DozeHost#isPulsePending=" + mDozeHost.isPulsePending());
pw.println("DozeSensors:");
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 9cd149b..5694f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -18,7 +18,7 @@
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION;
-import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
import android.animation.Animator;
@@ -67,7 +67,7 @@
private final Parent mParent;
@Complication.Category
private final int mCategory;
- private final int mMargin;
+ private final int mDefaultMargin;
/**
* Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
@@ -75,7 +75,7 @@
*/
ViewEntry(View view, ComplicationLayoutParams layoutParams,
TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
- int margin) {
+ int defaultMargin) {
mView = view;
// Views that are generated programmatically do not have a unique id assigned to them
// at construction. A new id is assigned here to enable ConstraintLayout relative
@@ -86,7 +86,7 @@
mTouchInsetSession = touchSession;
mCategory = category;
mParent = parent;
- mMargin = margin;
+ mDefaultMargin = defaultMargin;
touchSession.addViewToTracking(mView);
}
@@ -195,18 +195,19 @@
}
if (!isRoot) {
+ final int margin = mLayoutParams.getMargin(mDefaultMargin);
switch(direction) {
case ComplicationLayoutParams.DIRECTION_DOWN:
- params.setMargins(0, mMargin, 0, 0);
+ params.setMargins(0, margin, 0, 0);
break;
case ComplicationLayoutParams.DIRECTION_UP:
- params.setMargins(0, 0, 0, mMargin);
+ params.setMargins(0, 0, 0, margin);
break;
case ComplicationLayoutParams.DIRECTION_END:
- params.setMarginStart(mMargin);
+ params.setMarginStart(margin);
break;
case ComplicationLayoutParams.DIRECTION_START:
- params.setMarginEnd(mMargin);
+ params.setMarginEnd(margin);
break;
}
}
@@ -263,7 +264,7 @@
private final ComplicationLayoutParams mLayoutParams;
private final int mCategory;
private Parent mParent;
- private int mMargin;
+ private int mDefaultMargin;
Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
ComplicationLayoutParams lp, @Complication.Category int category) {
@@ -302,8 +303,8 @@
* Sets the margin that will be applied in the direction the complication is laid out
* towards.
*/
- Builder setMargin(int margin) {
- mMargin = margin;
+ Builder setDefaultMargin(int margin) {
+ mDefaultMargin = margin;
return this;
}
@@ -312,7 +313,7 @@
*/
ViewEntry build() {
return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
- mMargin);
+ mDefaultMargin);
}
}
@@ -472,7 +473,7 @@
}
private final ConstraintLayout mLayout;
- private final int mMargin;
+ private final int mDefaultMargin;
private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
private final TouchInsetManager.TouchInsetSession mSession;
@@ -483,12 +484,12 @@
/** */
@Inject
public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
- @Named(COMPLICATION_MARGIN) int margin,
+ @Named(COMPLICATION_MARGIN_DEFAULT) int defaultMargin,
TouchInsetManager.TouchInsetSession session,
@Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration,
@Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) {
mLayout = layout;
- mMargin = margin;
+ mDefaultMargin = defaultMargin;
mSession = session;
mFadeInDuration = fadeInDuration;
mFadeOutDuration = fadeOutDuration;
@@ -537,7 +538,7 @@
}
final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
- .setMargin(mMargin);
+ .setDefaultMargin(mDefaultMargin);
// Add position group if doesn't already exist
final int position = lp.getPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 8e8cb72..a21eb19 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -51,6 +51,8 @@
private static final int FIRST_POSITION = POSITION_TOP;
private static final int LAST_POSITION = POSITION_END;
+ private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
DIRECTION_UP,
@@ -77,6 +79,8 @@
private final int mWeight;
+ private final int mMargin;
+
private final boolean mSnapToGuide;
// Do not allow specifying opposite positions
@@ -106,7 +110,24 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight) {
- this(width, height, position, direction, weight, false);
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin) {
+ this(width, height, position, direction, weight, margin, false);
}
/**
@@ -127,6 +148,28 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight, boolean snapToGuide) {
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+ * will be automatically set to align with a predetermined guide for that
+ * side. For example, if the complication is aligned to the top end and
+ * direction is down, then the width of the complication will be set to span
+ * from the end of the parent to the guide.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin, boolean snapToGuide) {
super(width, height);
if (!validatePosition(position)) {
@@ -142,6 +185,8 @@
mWeight = weight;
+ mMargin = margin;
+
mSnapToGuide = snapToGuide;
}
@@ -153,6 +198,7 @@
mPosition = source.mPosition;
mDirection = source.mDirection;
mWeight = source.mWeight;
+ mMargin = source.mMargin;
mSnapToGuide = source.mSnapToGuide;
}
@@ -215,6 +261,14 @@
}
/**
+ * Returns the margin to apply between complications, or the given default if no margin is
+ * specified.
+ */
+ public int getMargin(int defaultMargin) {
+ return mMargin == MARGIN_UNSPECIFIED ? defaultMargin : mMargin;
+ }
+
+ /**
* Returns whether the complication's dimension perpendicular to direction should be
* automatically set.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 2503d3c..821e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -28,6 +28,9 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -158,17 +161,38 @@
private final Context mContext;
private final ControlsComponent mControlsComponent;
+ private final UiEventLogger mUiEventLogger;
+
+ @VisibleForTesting
+ public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+ DREAM_HOME_CONTROLS_TAPPED(1212);
+
+ private final int mId;
+
+ DreamOverlayEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
@Inject
DreamHomeControlsChipViewController(
@Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
ActivityStarter activityStarter,
Context context,
- ControlsComponent controlsComponent) {
+ ControlsComponent controlsComponent,
+ UiEventLogger uiEventLogger) {
super(view);
mActivityStarter = activityStarter;
mContext = context;
mControlsComponent = controlsComponent;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -184,6 +208,8 @@
private void onClickHomeControls(View v) {
if (DEBUG) Log.d(TAG, "home controls complication tapped");
+ mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+
final Intent intent = new Intent(mContext, ControlsActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(ControlsUiController.EXTRA_ANIMATE, true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 11d89d2..c9fecc9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -37,7 +37,7 @@
@Module
public abstract class ComplicationHostViewModule {
public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
- public static final String COMPLICATION_MARGIN = "complication_margin";
+ public static final String COMPLICATION_MARGIN_DEFAULT = "complication_margin_default";
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
@@ -58,7 +58,7 @@
}
@Provides
- @Named(COMPLICATION_MARGIN)
+ @Named(COMPLICATION_MARGIN_DEFAULT)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationPadding(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 759d6ec..7d2ce51 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -59,11 +59,11 @@
@Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
static ComplicationLayoutParams provideClockTimeLayoutParams() {
return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
}
/**
@@ -73,12 +73,12 @@
@Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
}
/**
@@ -103,11 +103,12 @@
@Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
static ComplicationLayoutParams provideSmartspaceLayoutParams() {
return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
- true /*snapToGuide*/);
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+ 0,
+ true /*snapToGuide*/);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 43742a8..7e08da7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -68,7 +68,12 @@
public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
- // next id: 112
+ public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
+ false);
+
+ public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
+
+ // next id: 114
/***************************************/
// 200 - keyguard/lockscreen
@@ -225,7 +230,8 @@
new ReleasedFlag(1000);
public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
- public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false);
+ public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
+ new UnreleasedFlag(1002, /* teamfood= */ true);
// 1100 - windowing
@Keep
@@ -271,6 +277,10 @@
public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
+ @Keep
+ public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+ new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 38b98eb..da0b910 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -512,9 +511,9 @@
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
synchronized (KeyguardViewMediator.this) {
- if (!showing && mPendingPinLock) {
+ if (!visible && mPendingPinLock) {
Log.i(TAG, "PIN lock requested, starting keyguard");
// Bring the keyguard back in order to show the PIN lock
@@ -804,9 +803,6 @@
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
- } else if (trustAgentsEnabled
- && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
- return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
@@ -987,9 +983,11 @@
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
- if (mUnoccludeAnimator != null) {
- mUnoccludeAnimator.cancel();
- }
+ mContext.getMainExecutor().execute(() -> {
+ if (mUnoccludeAnimator != null) {
+ mUnoccludeAnimator.cancel();
+ }
+ });
setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
@@ -1808,7 +1806,6 @@
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
- mUpdateMonitor.setKeyguardOccluded(isOccluded);
mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
&& mDeviceInteractive);
adjustStatusBarLocked();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 4c4b588..45b668e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -157,7 +157,11 @@
}
}
dozeHost.addCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+ trySendWithFailureLogging(
+ statusBarStateController.isDozing,
+ TAG,
+ "initial isDozing",
+ )
awaitClose { dozeHost.removeCallback(callback) }
}
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 95acc0b..01cd3e2 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
@@ -104,7 +104,6 @@
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index]::class,
icon = visibleState.icon,
- contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
)
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eff1469..eb6bb92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -17,8 +17,7 @@
package com.android.systemui.keyguard.domain.model
-import androidx.annotation.StringRes
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
@@ -35,11 +34,6 @@
/** Identifier for the affordance this is modeling. */
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
/** An icon for the affordance. */
- val icon: ContainedDrawable,
- /**
- * Resource ID for a string to use for the accessibility content description text of the
- * affordance.
- */
- @StringRes val contentDescriptionResourceId: Int,
+ val icon: Icon,
) : KeyguardQuickAffordanceModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index ac2c9b1..89604f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -23,7 +23,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.dagger.ControlsComponent
@@ -122,8 +123,14 @@
visibility == ControlsComponent.Visibility.AVAILABLE
) {
KeyguardQuickAffordanceConfig.State.Visible(
- icon = ContainedDrawable.WithResource(iconResourceId),
- contentDescriptionResourceId = component.getTileTitleId(),
+ icon =
+ Icon.Resource(
+ res = iconResourceId,
+ contentDescription =
+ ContentDescription.Resource(
+ res = component.getTileTitleId(),
+ ),
+ ),
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 8fb952c..8e1c6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,9 +18,8 @@
package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Intent
-import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -44,12 +43,7 @@
/** An affordance is visible. */
data class Visible(
/** An icon for the affordance. */
- val icon: ContainedDrawable,
- /**
- * Resource ID for a string to use for the accessibility content description text of the
- * affordance.
- */
- @StringRes val contentDescriptionResourceId: Int,
+ val icon: Icon,
) : State()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index c8e5e4a..d97deaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import javax.inject.Inject
@@ -76,8 +77,14 @@
private fun state(): KeyguardQuickAffordanceConfig.State {
return if (controller.isEnabledForLockScreenButton) {
KeyguardQuickAffordanceConfig.State.Visible(
- icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
- contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_qr_code_scanner,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_qr_code_scanner_button,
+ ),
+ ),
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 885af33..9196b09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -26,7 +26,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
@@ -100,8 +101,14 @@
): KeyguardQuickAffordanceConfig.State {
return if (isFeatureEnabled && hasCard && tileIcon != null) {
KeyguardQuickAffordanceConfig.State.Visible(
- icon = ContainedDrawable.WithDrawable(tileIcon),
- contentDescriptionResourceId = R.string.accessibility_wallet_button,
+ icon =
+ Icon.Loaded(
+ drawable = tileIcon,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ ),
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index c4e3d4e..65b85c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -31,7 +31,7 @@
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Interpolators
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -236,10 +236,7 @@
}
}
- when (viewModel.icon) {
- is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
- is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
- }
+ IconViewBinder.bind(viewModel.icon, view)
view.drawable.setTint(
Utils.getColorAttrDefaultColor(
@@ -250,7 +247,6 @@
view.backgroundTintList =
Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
- view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
view.setOnClickListener(OnClickListener(viewModel, falsingManager))
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index e3ebac6..970ee4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -116,7 +116,6 @@
isVisible = true,
animateReveal = animateReveal,
icon = icon,
- contentDescriptionResourceId = contentDescriptionResourceId,
onClicked = { parameters ->
quickAffordanceInteractor.onQuickAffordanceClicked(
configKey = parameters.configKey,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index b1de27d..0971f13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -16,9 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
@@ -28,8 +27,7 @@
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
- val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
- @StringRes val contentDescriptionResourceId: Int = 0,
+ val icon: Icon = Icon.Resource(res = 0, contentDescription = null),
val onClicked: (OnClickedParameters) -> Unit = {},
val isClickable: Boolean = false,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
new file mode 100644
index 0000000..aef3471
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * A [com.android.systemui.log.LogBuffer] for keyguard-related stuff. Should be used mostly for
+ * adding temporary logs or logging from smaller classes when creating new separate log class might
+ * be an overkill.
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class KeyguardLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 29e2c1c..d298ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -43,7 +43,7 @@
@SysUISingleton
@DozeLog
public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
- return factory.create("DozeLog", 100);
+ return factory.create("DozeLog", 120);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -334,4 +334,14 @@
public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
return factory.create("BluetoothLog", 50);
}
+
+ /**
+ * Provides a {@link LogBuffer} for general keyguard-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardLog
+ public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardLog", 250);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 8368792..2cd564f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -449,7 +449,7 @@
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- val isCurVisibleMediaPlaying = MediaPlayerData.getMediaData(curVisibleMediaKey)?.isPlaying
+ val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
if (existingPlayer == null) {
val newPlayer = mediaControlPanelFactory.get()
newPlayer.attachPlayer(MediaViewHolder.create(
@@ -1042,15 +1042,6 @@
}
}
- fun getMediaData(mediaSortKey: MediaSortKey?): MediaData? {
- mediaData.forEach { (key, value) ->
- if (value == mediaSortKey) {
- return mediaData[key]?.data
- }
- }
- return null
- }
-
fun getMediaPlayer(key: String): MediaControlPanel? {
return mediaData.get(key)?.let { mediaPlayers.get(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index c6bd777..1ac2a07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -17,6 +17,7 @@
import android.app.ActivityOptions
import android.content.Intent
+import android.content.res.Configuration
import android.media.projection.IMediaProjection
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
import android.os.Binder
@@ -24,85 +25,73 @@
import android.os.IBinder
import android.os.ResultReceiver
import android.os.UserHandle
-import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
import com.android.systemui.mediaprojection.appselector.data.RecentTask
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.AsyncActivityLauncher
-import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
import javax.inject.Inject
class MediaProjectionAppSelectorActivity(
+ private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
private val activityLauncher: AsyncActivityLauncher,
- private val controller: MediaProjectionAppSelectorController,
- private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, RecentTaskClickListener {
+) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
@Inject
constructor(
+ componentFactory: MediaProjectionAppSelectorComponent.Factory,
activityLauncher: AsyncActivityLauncher,
- controller: MediaProjectionAppSelectorController,
- recentTasksAdapterFactory: RecentTasksAdapter.Factory,
- ) : this(activityLauncher, controller, recentTasksAdapterFactory, null)
+ ) : this(componentFactory, activityLauncher, null)
- private var recentsRoot: ViewGroup? = null
- private var recentsProgress: View? = null
- private var recentsRecycler: RecyclerView? = null
+ private lateinit var configurationController: ConfigurationController
+ private lateinit var controller: MediaProjectionAppSelectorController
+ private lateinit var recentsViewController: MediaProjectionRecentsViewController
- override fun getLayoutResource() =
- R.layout.media_projection_app_selector
+ override fun getLayoutResource() = R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) {
- val queryIntent = Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
+ val component =
+ componentFactory.create(
+ activity = this,
+ view = this,
+ resultHandler = this
+ )
+
+ // Create a separate configuration controller for this activity as the configuration
+ // might be different from the global one
+ configurationController = component.configurationController
+ controller = component.controller
+ recentsViewController = component.recentsViewController
+
+ val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
// TODO(b/240939253): update copies
val title = getString(R.string.media_projection_dialog_service_title)
intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
- controller.init(this)
+ controller.init()
}
- private fun createRecentsView(parent: ViewGroup): ViewGroup {
- val recentsRoot = LayoutInflater.from(this)
- .inflate(R.layout.media_projection_recent_tasks, parent,
- /* attachToRoot= */ false) as ViewGroup
-
- recentsProgress = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_loader)
- recentsRecycler = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_recycler)
- recentsRecycler?.layoutManager = LinearLayoutManager(
- this, LinearLayoutManager.HORIZONTAL,
- /* reverseLayout= */false
- )
-
- val itemDecoration = HorizontalSpacerItemDecoration(
- resources.getDimensionPixelOffset(
- R.dimen.media_projection_app_selector_recents_padding
- )
- )
- recentsRecycler?.addItemDecoration(itemDecoration)
-
- return recentsRoot
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ configurationController.onConfigurationChanged(newConfig)
}
- override fun appliedThemeResId(): Int =
- R.style.Theme_SystemUI_MediaProjectionAppSelector
+ override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
override fun createListController(userHandle: UserHandle): ResolverListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
@@ -124,9 +113,9 @@
// is typically very fast, so we don't show any loaders.
// We wait for the activity to be launched to make sure that the window of the activity
// is created and ready to be captured.
- val activityStarted = activityLauncher
- .startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
- onTargetActivityLaunched(launchToken)
+ val activityStarted =
+ activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
+ returnSelectedApp(launchToken)
}
// Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -160,44 +149,27 @@
}
override fun bind(recentTasks: List<RecentTask>) {
- val recents = recentsRoot ?: return
- val progress = recentsProgress ?: return
- val recycler = recentsRecycler ?: return
-
- if (recentTasks.isEmpty()) {
- recents.visibility = View.GONE
- return
- }
-
- progress.visibility = View.GONE
- recycler.visibility = View.VISIBLE
- recents.visibility = View.VISIBLE
-
- recycler.adapter = recentTasksAdapterFactory.create(recentTasks, this)
+ recentsViewController.bind(recentTasks)
}
- override fun onRecentClicked(task: RecentTask, view: View) {
- // TODO(b/240924732) Handle clicking on a recent task
- }
-
- private fun onTargetActivityLaunched(launchToken: IBinder) {
+ override fun returnSelectedApp(launchCookie: IBinder) {
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
// The client requested to return the result in the result receiver instead of
// activity result, let's send the media projection to the result receiver
- val resultReceiver = intent
- .getParcelableExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
- ResultReceiver::class.java) as ResultReceiver
- val captureRegion = MediaProjectionCaptureTarget(launchToken)
- val data = Bundle().apply {
- putParcelable(KEY_CAPTURE_TARGET, captureRegion)
- }
+ val resultReceiver =
+ intent.getParcelableExtra(
+ EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ ResultReceiver::class.java
+ ) as ResultReceiver
+ val captureRegion = MediaProjectionCaptureTarget(launchCookie)
+ val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
resultReceiver.send(RESULT_OK, data)
} else {
// Return the media projection instance as activity result
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
- projection.launchCookie = launchToken
+ projection.launchCookie = launchCookie
val intent = Intent()
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
@@ -214,15 +186,13 @@
override fun shouldShowContentPreview() = false
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
- recentsRoot ?: createRecentsView(parent).also {
- recentsRoot = it
- }
+ recentsViewController.createView(parent)
companion object {
/**
- * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
- * the activity will send the [CaptureRegion] to the result receiver
- * instead of returning media projection instance through activity result.
+ * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will
+ * send the [CaptureRegion] to the result receiver instead of returning media projection
+ * instance through activity result.
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
const val KEY_CAPTURE_TARGET = "capture_region"
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 0359c63..17ebfec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -30,7 +30,10 @@
import androidx.core.view.GestureDetectorCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.util.concurrency.RepeatableExecutor
import javax.inject.Inject
@@ -72,7 +75,8 @@
/** ViewModel for seek bar in QS media player. */
class SeekBarViewModel @Inject constructor(
- @Background private val bgExecutor: RepeatableExecutor
+ @Background private val bgExecutor: RepeatableExecutor,
+ private val falsingManager: FalsingManager,
) {
private var _data = Progress(false, false, false, false, null, 0)
set(value) {
@@ -275,7 +279,7 @@
/** Gets a listener to attach to the seek bar to handle seeking. */
val seekBarListener: SeekBar.OnSeekBarChangeListener
get() {
- return SeekBarChangeListener(this)
+ return SeekBarChangeListener(this, falsingManager)
}
/** Attach touch handlers to the seek bar view. */
@@ -315,7 +319,8 @@
}
private class SeekBarChangeListener(
- val viewModel: SeekBarViewModel
+ val viewModel: SeekBarViewModel,
+ val falsingManager: FalsingManager,
) : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
@@ -328,6 +333,13 @@
}
override fun onStopTrackingTouch(bar: SeekBar) {
+ // in addition to the normal functionality of both functions.
+ // isFalseTouch returns true if there is a real/false tap since it is not a move.
+ // isFalseTap returns true if there is a real/false move since it is not a tap.
+ if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
+ falsingManager.isFalseTap(LOW_PENALTY)) {
+ viewModel.onSeekFalse()
+ }
viewModel.onSeek(bar.progress.toLong())
}
}
@@ -340,7 +352,7 @@
*/
private class SeekBarTouchListener(
private val viewModel: SeekBarViewModel,
- private val bar: SeekBar
+ private val bar: SeekBar,
) : View.OnTouchListener, GestureDetector.OnGestureListener {
// Gesture detector helps decide which touch events to intercept.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
deleted file mode 100644
index 185b4fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ /dev/null
@@ -1,89 +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.media.dagger
-
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
-import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import javax.inject.Qualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class MediaProjectionAppSelector
-
-@Module
-abstract class MediaProjectionModule {
-
- @Binds
- @IntoMap
- @ClassKey(MediaProjectionAppSelectorActivity::class)
- abstract fun provideMediaProjectionAppSelectorActivity(
- activity: MediaProjectionAppSelectorActivity
- ): Activity
-
- @Binds
- abstract fun bindRecentTaskThumbnailLoader(
- impl: ActivityTaskManagerThumbnailLoader
- ): RecentTaskThumbnailLoader
-
- @Binds
- abstract fun bindRecentTaskListProvider(
- impl: ShellRecentTaskListProvider
- ): RecentTaskListProvider
-
- @Binds
- abstract fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
-
- companion object {
- @Provides
- fun provideController(
- recentTaskListProvider: RecentTaskListProvider,
- context: Context,
- @MediaProjectionAppSelector scope: CoroutineScope
- ): MediaProjectionAppSelectorController {
- val appSelectorComponentName =
- ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
-
- return MediaProjectionAppSelectorController(
- recentTaskListProvider,
- scope,
- appSelectorComponentName
- )
- }
-
- @MediaProjectionAppSelector
- @Provides
- fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
- CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 792ae7c..c3de94f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -76,29 +75,6 @@
isAppIcon = false
)
}
-
- /**
- * Sets an icon to be displayed by the given view.
- *
- * @param iconSize the size in pixels that the icon should be. If null, the size of
- * [appIconView] will not be adjusted.
- */
- fun setIcon(
- appIconView: CachingIconView,
- icon: Drawable,
- iconContentDescription: CharSequence,
- iconSize: Int? = null,
- ) {
- iconSize?.let { size ->
- val lp = appIconView.layoutParams
- lp.width = size
- lp.height = size
- appIconView.layoutParams = lp
- }
-
- appIconView.contentDescription = iconContentDescription
- appIconView.setImageDrawable(icon)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index dfd9e22..8fc5519 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -30,6 +30,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
@@ -146,20 +147,17 @@
)
val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
- val iconSize = context.resources.getDimensionPixelSize(
+ val iconPadding =
if (iconInfo.isAppIcon) {
- R.dimen.media_ttt_icon_size_receiver
+ 0
} else {
- R.dimen.media_ttt_generic_icon_size_receiver
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
}
- )
- MediaTttUtils.setIcon(
- currentView.requireViewById(R.id.app_icon),
- iconDrawable,
- iconContentDescription,
- iconSize,
- )
+ val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
+ iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
+ iconView.setImageDrawable(iconDrawable)
+ iconView.contentDescription = iconContentDescription
}
override fun animateViewIn(view: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 007eb8f..11c5528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -29,6 +29,7 @@
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
@@ -53,7 +54,7 @@
* chip is shown when a user is transferring media to/from this device and a receiver device.
*/
@SysUISingleton
-class MediaTttChipControllerSender @Inject constructor(
+open class MediaTttChipControllerSender @Inject constructor(
commandQueue: CommandQueue,
context: Context,
@MediaTttSenderLogger logger: MediaTttLogger,
@@ -145,11 +146,9 @@
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
- MediaTttUtils.setIcon(
- currentView.requireViewById(R.id.app_icon),
- iconInfo.drawable,
- iconInfo.contentDescription
- )
+ val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
+ iconView.setImageDrawable(iconInfo.drawable)
+ iconView.contentDescription = iconInfo.contentDescription
// Text
val otherDeviceName = newInfo.routeInfo.name.toString()
@@ -196,7 +195,19 @@
)
}
- override fun removeView(removalReason: String) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ ViewHierarchyAnimator.animateRemoval(
+ view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_DURATION,
+ onAnimationEnd,
+ )
+ // TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the
+ // animateChipOut matches the animateChipIn.
+ }
+
+ override fun shouldIgnoreViewRemoval(removalReason: String): Boolean {
// Don't remove the chip if we're in progress or succeeded, since the user should still be
// able to see the status of the transfer. (But do remove it if it's finally timed out.)
val transferStatus = info?.state?.transferStatus
@@ -208,9 +219,9 @@
logger.logRemovalBypass(
removalReason, bypassReason = "transferStatus=${transferStatus.name}"
)
- return
+ return true
}
- super.removeView(removalReason)
+ return false
}
private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
new file mode 100644
index 0000000..7fd100f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.mediaprojection.appselector
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Qualifier
+import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
+
+@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
+
+@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+interface MediaProjectionModule {
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionAppSelectorActivity::class)
+ fun provideMediaProjectionAppSelectorActivity(
+ activity: MediaProjectionAppSelectorActivity
+ ): Activity
+}
+
+/** Scoped values for [MediaProjectionAppSelectorComponent].
+ * We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider]
+ * could be reused. */
+@Module
+interface MediaProjectionAppSelectorModule {
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindRecentTaskThumbnailLoader(
+ impl: ActivityTaskManagerThumbnailLoader
+ ): RecentTaskThumbnailLoader
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+
+ companion object {
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun provideAppSelectorComponentName(context: Context): ComponentName =
+ ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun bindConfigurationController(
+ activity: MediaProjectionAppSelectorActivity
+ ): ConfigurationController = ConfigurationControllerImpl(activity)
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
+ CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+ }
+}
+
+@Subcomponent(modules = [MediaProjectionAppSelectorModule::class])
+@MediaProjectionAppSelectorScope
+interface MediaProjectionAppSelectorComponent {
+
+ /** Generates [MediaProjectionAppSelectorComponent]. */
+ @Subcomponent.Factory
+ interface Factory {
+ /**
+ * Create a factory to inject the activity into the graph
+ */
+ fun create(
+ @BindsInstance activity: MediaProjectionAppSelectorActivity,
+ @BindsInstance view: MediaProjectionAppSelectorView,
+ @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+ ): MediaProjectionAppSelectorComponent
+ }
+
+ val controller: MediaProjectionAppSelectorController
+ val recentsViewController: MediaProjectionRecentsViewController
+
+ @MediaProjectionAppSelector val configurationController: ConfigurationController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 2b381a9..d744a40b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,20 +17,22 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class MediaProjectionAppSelectorController(
+@MediaProjectionAppSelectorScope
+class MediaProjectionAppSelectorController @Inject constructor(
private val recentTaskListProvider: RecentTaskListProvider,
+ private val view: MediaProjectionAppSelectorView,
@MediaProjectionAppSelector private val scope: CoroutineScope,
- private val appSelectorComponentName: ComponentName
+ @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
) {
- fun init(view: MediaProjectionAppSelectorView) {
+ fun init() {
scope.launch {
val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
view.bind(tasks)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
new file mode 100644
index 0000000..93c3bce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.mediaprojection.appselector
+
+import android.os.IBinder
+
+/**
+ * Interface that allows to continue the media projection flow and return the selected app
+ * result to the original caller.
+ */
+interface MediaProjectionAppSelectorResultHandler {
+ /**
+ * Return selected app to the original caller of the media projection app picker.
+ * @param launchCookie launch cookie of the launched activity of the target app
+ */
+ fun returnSelectedApp(launchCookie: IBinder)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
new file mode 100644
index 0000000..c816446
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.app.ActivityOptions
+import android.app.IActivityTaskManager
+import android.graphics.Rect
+import android.os.Binder
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import javax.inject.Inject
+
+/**
+ * Controller that handles view of the recent apps selector in the media projection activity.
+ * It is responsible for creating and updating recent apps view.
+ */
+@MediaProjectionAppSelectorScope
+class MediaProjectionRecentsViewController
+@Inject
+constructor(
+ private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
+ private val taskViewSizeProvider: TaskPreviewSizeProvider,
+ private val activityTaskManager: IActivityTaskManager,
+ private val resultHandler: MediaProjectionAppSelectorResultHandler,
+) : RecentTaskClickListener, TaskPreviewSizeListener {
+
+ private var views: Views? = null
+ private var lastBoundData: List<RecentTask>? = null
+
+ init {
+ taskViewSizeProvider.addCallback(this)
+ }
+
+ fun createView(parent: ViewGroup): ViewGroup =
+ views?.root ?: createRecentViews(parent).also {
+ views = it
+ lastBoundData?.let { recents -> bind(recents) }
+ }.root
+
+ fun bind(recentTasks: List<RecentTask>) {
+ views?.apply {
+ if (recentTasks.isEmpty()) {
+ root.visibility = View.GONE
+ return
+ }
+
+ progress.visibility = View.GONE
+ recycler.visibility = View.VISIBLE
+ root.visibility = View.VISIBLE
+
+ recycler.adapter =
+ recentTasksAdapterFactory.create(
+ recentTasks,
+ this@MediaProjectionRecentsViewController
+ )
+ }
+
+ lastBoundData = recentTasks
+ }
+
+ private fun createRecentViews(parent: ViewGroup): Views {
+ val recentsRoot =
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
+ as ViewGroup
+
+ val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+ container.setTaskHeightSize()
+
+ val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
+ val recycler =
+ recentsRoot.requireViewById<RecyclerView>(R.id.media_projection_recent_tasks_recycler)
+ recycler.layoutManager =
+ LinearLayoutManager(
+ parent.context,
+ LinearLayoutManager.HORIZONTAL,
+ /* reverseLayout= */ false
+ )
+
+ val itemDecoration =
+ HorizontalSpacerItemDecoration(
+ parent.resources.getDimensionPixelOffset(
+ R.dimen.media_projection_app_selector_recents_padding
+ )
+ )
+ recycler.addItemDecoration(itemDecoration)
+
+ return Views(recentsRoot, container, progress, recycler)
+ }
+
+ override fun onRecentAppClicked(task: RecentTask, view: View) {
+ val launchCookie = Binder()
+ val activityOptions =
+ ActivityOptions.makeScaleUpAnimation(
+ view,
+ /* startX= */ 0,
+ /* startY= */ 0,
+ view.width,
+ view.height
+ )
+ activityOptions.launchCookie = launchCookie
+
+ activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
+ resultHandler.returnSelectedApp(launchCookie)
+ }
+
+ override fun onTaskSizeChanged(size: Rect) {
+ views?.recentsContainer?.setTaskHeightSize()
+ }
+
+ private fun View.setTaskHeightSize() {
+ val thumbnailHeight = taskViewSizeProvider.size.height()
+ val itemHeight =
+ thumbnailHeight +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_size
+ ) +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_margin
+ ) * 2
+
+ layoutParams = layoutParams.apply { height = itemHeight }
+ }
+
+ private class Views(
+ val root: ViewGroup,
+ val recentsContainer: View,
+ val progress: View,
+ val recycler: RecyclerView
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
new file mode 100644
index 0000000..b682bd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.graphics.BitmapShader
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowManager
+import androidx.core.content.getSystemService
+import androidx.core.content.res.use
+import com.android.internal.R as AndroidR
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+
+/**
+ * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
+ * It handles proper cropping and positioning of the thumbnail using [PreviewPositionHelper].
+ */
+class MediaProjectionTaskView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ View(context, attrs, defStyleAttr) {
+
+ private val defaultBackgroundColor: Int
+
+ init {
+ val backgroundColorAttribute = intArrayOf(android.R.attr.colorBackgroundFloating)
+ defaultBackgroundColor =
+ context.obtainStyledAttributes(backgroundColorAttribute).use {
+ it.getColor(/* index= */ 0, /* defValue= */ Color.BLACK)
+ }
+ }
+
+ private val windowManager: WindowManager = context.getSystemService()!!
+ private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val backgroundPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor }
+ private val cornerRadius =
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_rounded_corners
+ )
+ private val previewPositionHelper = PreviewPositionHelper()
+ private val previewRect = Rect()
+
+ private var task: RecentTask? = null
+ private var thumbnailData: ThumbnailData? = null
+
+ private var bitmapShader: BitmapShader? = null
+
+ fun bindTask(task: RecentTask?, thumbnailData: ThumbnailData?) {
+ this.task = task
+ this.thumbnailData = thumbnailData
+
+ // Strip alpha channel to make sure that the color is not semi-transparent
+ val color = (task?.colorBackground ?: Color.BLACK) or 0xFF000000.toInt()
+
+ paint.color = color
+ backgroundPaint.color = color
+
+ refresh()
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ updateThumbnailMatrix()
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ // Always draw the background since the snapshots might be translucent or partially empty
+ // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
+ // split screen).
+ canvas.drawRoundRect(
+ 0f,
+ 1f,
+ width.toFloat(),
+ (height - 1).toFloat(),
+ cornerRadius.toFloat(),
+ cornerRadius.toFloat(),
+ backgroundPaint
+ )
+
+ val drawBackgroundOnly = task == null || bitmapShader == null || thumbnailData == null
+ if (drawBackgroundOnly) {
+ return
+ }
+
+ // Draw the task thumbnail using bitmap shader in the paint
+ canvas.drawRoundRect(
+ 0f,
+ 0f,
+ width.toFloat(),
+ height.toFloat(),
+ cornerRadius.toFloat(),
+ cornerRadius.toFloat(),
+ paint
+ )
+ }
+
+ private fun refresh() {
+ val thumbnailBitmap = thumbnailData?.thumbnail
+
+ if (thumbnailBitmap != null) {
+ thumbnailBitmap.prepareToDraw()
+ bitmapShader =
+ BitmapShader(thumbnailBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ paint.shader = bitmapShader
+ updateThumbnailMatrix()
+ } else {
+ bitmapShader = null
+ paint.shader = null
+ }
+
+ invalidate()
+ }
+
+ private fun updateThumbnailMatrix() {
+ previewPositionHelper.isOrientationChanged = false
+
+ val bitmapShader = bitmapShader ?: return
+ val thumbnailData = thumbnailData ?: return
+ val display = context.display ?: return
+ val windowMetrics = windowManager.maximumWindowMetrics
+
+ previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
+
+ val currentRotation: Int = display.rotation
+ val displayWidthPx = windowMetrics.bounds.width()
+ val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ val isTablet = isTablet(context)
+ val taskbarSize =
+ if (isTablet) {
+ resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ } else {
+ 0
+ }
+
+ previewPositionHelper.updateThumbnailMatrix(
+ previewRect,
+ thumbnailData,
+ measuredWidth,
+ measuredHeight,
+ displayWidthPx,
+ taskbarSize,
+ isTablet,
+ currentRotation,
+ isRtl
+ )
+
+ bitmapShader.setLocalMatrix(previewPositionHelper.matrix)
+ paint.shader = bitmapShader
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index ec5abc7..15cfeee 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -16,15 +16,17 @@
package com.android.systemui.mediaprojection.appselector.view
+import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -32,19 +34,27 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-class RecentTaskViewHolder @AssistedInject constructor(
- @Assisted root: ViewGroup,
+class RecentTaskViewHolder
+@AssistedInject
+constructor(
+ @Assisted private val root: ViewGroup,
private val iconLoader: AppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ private val taskViewSizeProvider: TaskPreviewSizeProvider,
@MediaProjectionAppSelector private val scope: CoroutineScope
-) : RecyclerView.ViewHolder(root) {
+) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
+ val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail)
private val iconView: ImageView = root.requireViewById(R.id.task_icon)
- private val thumbnailView: ImageView = root.requireViewById(R.id.task_thumbnail)
private var job: Job? = null
+ init {
+ updateThumbnailSize()
+ }
+
fun bind(task: RecentTask, onClick: (View) -> Unit) {
+ taskViewSizeProvider.addCallback(this)
job?.cancel()
job =
@@ -57,20 +67,33 @@
}
launch {
val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
- thumbnailView.setImageBitmap(thumbnail?.thumbnail)
+ thumbnailView.bindTask(task, thumbnail)
}
}
- thumbnailView.setOnClickListener(onClick)
+ root.setOnClickListener(onClick)
}
fun onRecycled() {
+ taskViewSizeProvider.removeCallback(this)
iconView.setImageDrawable(null)
- thumbnailView.setImageBitmap(null)
+ thumbnailView.bindTask(null, null)
job?.cancel()
job = null
}
+ override fun onTaskSizeChanged(size: Rect) {
+ updateThumbnailSize()
+ }
+
+ private fun updateThumbnailSize() {
+ thumbnailView.layoutParams =
+ thumbnailView.layoutParams.apply {
+ width = taskViewSizeProvider.size.width()
+ height = taskViewSizeProvider.size.height()
+ }
+ }
+
@AssistedFactory
fun interface Factory {
fun create(root: ViewGroup): RecentTaskViewHolder
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
index ec9cfa8..6af50a0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
@@ -26,7 +26,9 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-class RecentTasksAdapter @AssistedInject constructor(
+class RecentTasksAdapter
+@AssistedInject
+constructor(
@Assisted private val items: List<RecentTask>,
@Assisted private val listener: RecentTaskClickListener,
private val viewHolderFactory: RecentTaskViewHolder.Factory
@@ -34,8 +36,8 @@
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentTaskViewHolder {
val taskItem =
- LayoutInflater.from(parent.context)
- .inflate(R.layout.media_projection_task_item, null) as ViewGroup
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.media_projection_task_item, parent, false) as ViewGroup
return viewHolderFactory.create(taskItem)
}
@@ -43,7 +45,7 @@
override fun onBindViewHolder(holder: RecentTaskViewHolder, position: Int) {
val task = items[position]
holder.bind(task, onClick = {
- listener.onRecentClicked(task, holder.itemView)
+ listener.onRecentAppClicked(task, holder.itemView)
})
}
@@ -54,7 +56,7 @@
}
interface RecentTaskClickListener {
- fun onRecentClicked(task: RecentTask, view: View)
+ fun onRecentAppClicked(task: RecentTask, view: View)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
new file mode 100644
index 0000000..88d5eaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.WindowManager
+import com.android.internal.R as AndroidR
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class TaskPreviewSizeProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val windowManager: WindowManager,
+ configurationController: ConfigurationController
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+
+ /** Returns the size of the task preview on the screen in pixels */
+ val size: Rect = calculateSize()
+
+ private val listeners = arrayListOf<TaskPreviewSizeListener>()
+
+ init {
+ configurationController.addCallback(this)
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ val newSize = calculateSize()
+ if (newSize != size) {
+ size.set(newSize)
+ listeners.forEach { it.onTaskSizeChanged(size) }
+ }
+ }
+
+ private fun calculateSize(): Rect {
+ val windowMetrics = windowManager.maximumWindowMetrics
+ val maximumWindowHeight = windowMetrics.bounds.height()
+ val width = windowMetrics.bounds.width()
+ var height = maximumWindowHeight
+
+ val isTablet = isTablet(context)
+ if (isTablet) {
+ val taskbarSize =
+ context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ height -= taskbarSize
+ }
+
+ val previewSize = Rect(0, 0, width, height)
+ val scale = (height / maximumWindowHeight.toFloat()) / SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO
+ previewSize.scale(scale)
+
+ return previewSize
+ }
+
+ override fun addCallback(listener: TaskPreviewSizeListener) {
+ listeners += listener
+ }
+
+ override fun removeCallback(listener: TaskPreviewSizeListener) {
+ listeners -= listener
+ }
+
+ interface TaskPreviewSizeListener {
+ fun onTaskSizeChanged(size: Rect)
+ }
+}
+
+/**
+ * How many times smaller the task preview should be on the screen comparing to the height of the
+ * screen
+ */
+private const val SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO = 4f
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3789cbb..029cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -142,6 +142,9 @@
boolean isOldConfigTablet = mIsTablet;
mIsTablet = isTablet(mContext);
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+ if (mTaskbarDelegate.isInitialized()) {
+ mTaskbarDelegate.onConfigurationChanged(newConfig);
+ }
// If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
if (largeScreenChanged && updateNavbarForTaskbar()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9e0c496..73fc21e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -40,7 +40,6 @@
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
-import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,7 +86,7 @@
@SysUISingleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
- ComponentCallbacks, Dumpable {
+ Dumpable {
private static final String TAG = TaskbarDelegate.class.getSimpleName();
private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -225,7 +224,6 @@
// Initialize component callback
Display display = mDisplayManager.getDisplay(displayId);
mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
- mWindowContext.registerComponentCallbacks(this);
mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
// Set initial state for any listeners
updateSysuiFlags();
@@ -233,6 +231,7 @@
mLightBarController.setNavigationBar(mLightBarTransitionsController);
mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
+ mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration());
mInitialized = true;
}
@@ -247,10 +246,7 @@
mNavBarHelper.destroy();
mEdgeBackGestureHandler.onNavBarDetached();
mScreenPinningNotify = null;
- if (mWindowContext != null) {
- mWindowContext.unregisterComponentCallbacks(this);
- mWindowContext = null;
- }
+ mWindowContext = null;
mAutoHideController.setNavigationBar(null);
mLightBarTransitionsController.destroy();
mLightBarController.setNavigationBar(null);
@@ -267,8 +263,9 @@
}
/**
- * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
- * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
+ * Returns {@code true} if this taskBar is {@link #init(int)}.
+ * Returns {@code false} if this taskbar has not yet been {@link #init(int)}
+ * or has been {@link #destroy()}.
*/
public boolean isInitialized() {
return mInitialized;
@@ -460,15 +457,11 @@
return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
- @Override
public void onConfigurationChanged(Configuration configuration) {
mEdgeBackGestureHandler.onConfigurationChanged(configuration);
}
@Override
- public void onLowMemory() {}
-
- @Override
public void showPinningEnterExitToast(boolean entering) {
updateSysuiFlags();
if (mScreenPinningNotify == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index b26b42c..a8799c7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -955,7 +956,7 @@
mStartingQuickstepRotation != rotation;
}
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (mStartingQuickstepRotation > -1) {
updateDisabledForQuickstep(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 184089f7..6517ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -105,6 +105,7 @@
private final Rect mClippingRect = new Rect();
private ViewGroup mMediaHostView;
private boolean mShouldMoveMediaOnExpansion = true;
+ private boolean mUsingCombinedHeaders = false;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -148,6 +149,10 @@
}
}
+ void setUsingCombinedHeaders(boolean usingCombinedHeaders) {
+ mUsingCombinedHeaders = usingCombinedHeaders;
+ }
+
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -371,7 +376,9 @@
protected void updatePadding() {
final Resources res = mContext.getResources();
- int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+ int paddingTop = res.getDimensionPixelSize(
+ mUsingCombinedHeaders ? R.dimen.qs_panel_padding_top_combined_headers
+ : R.dimen.qs_panel_padding_top);
int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
setPaddingRelative(getPaddingStart(),
paddingTop,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 18bd6b7..f6db775 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
+import static com.android.systemui.flags.Flags.COMBINED_QS_HEADERS;
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
@@ -27,6 +28,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -79,7 +81,8 @@
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mTunerService = tunerService;
@@ -93,6 +96,7 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mView.setUsingCombinedHeaders(featureFlags.isEnabled(COMBINED_QS_HEADERS));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ded466a..2727c83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -23,8 +23,8 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
import android.metrics.LogMaker;
-import android.util.Log;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,6 +75,7 @@
@Nullable
private Consumer<Boolean> mMediaVisibilityChangedListener;
+ @Orientation
private int mLastOrientation;
private String mCachedSpecs = "";
@Nullable
@@ -88,21 +89,16 @@
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
+ mQSLogger.logOnConfigurationChanged(
+ /* lastOrientation= */ mLastOrientation,
+ /* newOrientation= */ newConfig.orientation,
+ /* containerName= */ mView.getDumpableTag());
+
mShouldUseSplitNotificationShade =
- LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- // Logging to aid the investigation of b/216244185.
- Log.d(TAG,
- "onConfigurationChange: "
- + "mShouldUseSplitNotificationShade="
- + mShouldUseSplitNotificationShade + ", "
- + "newConfig.windowConfiguration="
- + newConfig.windowConfiguration);
- mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
- mView.getDumpableTag());
- if (newConfig.orientation != mLastOrientation) {
- mLastOrientation = newConfig.orientation;
- switchTileLayout(false);
- }
+ LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+ mLastOrientation = newConfig.orientation;
+
+ switchTileLayoutIfNeeded();
onConfigurationChanged();
}
};
@@ -334,6 +330,10 @@
}
}
+ private void switchTileLayoutIfNeeded() {
+ switchTileLayout(/* force= */ false);
+ }
+
boolean switchTileLayout(boolean force) {
/* Whether or not the panel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 899e57d..66be00d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,15 +25,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -110,16 +101,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.wm.shell.back.BackAnimation;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -151,10 +133,8 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final Optional<Pip> mPipOptional;
+ private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final Optional<SplitScreen> mSplitScreenOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -164,14 +144,8 @@
private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
- private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
- private final ShellTransitions mShellTransitions;
- private final Optional<StartingSurface> mStartingSurface;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
- private final Optional<RecentTasks> mRecentTasks;
- private final Optional<BackAnimation> mBackAnimation;
- private final Optional<DesktopMode> mDesktopModeOptional;
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -456,36 +430,10 @@
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
- mPipOptional.ifPresent((pip) -> params.putBinder(
- KEY_EXTRA_SHELL_PIP,
- pip.createExternalInterface().asBinder()));
- mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
- KEY_EXTRA_SHELL_SPLIT_SCREEN,
- splitscreen.createExternalInterface().asBinder()));
- mFloatingTasksOptional.ifPresent(floatingTasks -> params.putBinder(
- KEY_EXTRA_SHELL_FLOATING_TASKS,
- floatingTasks.createExternalInterface().asBinder()));
- mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
- KEY_EXTRA_SHELL_ONE_HANDED,
- onehanded.createExternalInterface().asBinder()));
- params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- mShellTransitions.createExternalInterface().asBinder());
- mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
- KEY_EXTRA_SHELL_STARTING_WINDOW,
- startingwindow.createExternalInterface().asBinder()));
- params.putBinder(
- KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+ params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
- mRecentTasks.ifPresent(recentTasks -> params.putBinder(
- KEY_EXTRA_RECENT_TASKS,
- recentTasks.createExternalInterface().asBinder()));
- mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
- KEY_EXTRA_SHELL_BACK_ANIMATION,
- backAnimation.createExternalInterface().asBinder()));
- mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
- KEY_EXTRA_SHELL_DESKTOP_MODE,
- desktopMode.createExternalInterface().asBinder())));
+ // Add all the interfaces exposed by the shell
+ mShellInterface.createExternalInterfaces(params);
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -559,21 +507,14 @@
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyService(Context context, CommandQueue commandQueue,
+ public OverviewProxyService(Context context,
+ CommandQueue commandQueue,
+ ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- Optional<Pip> pipOptional,
- Optional<SplitScreen> splitScreenOptional,
- Optional<FloatingTasks> floatingTasksOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<RecentTasks> recentTasks,
- Optional<BackAnimation> backAnimation,
- Optional<StartingSurface> startingSurface,
- Optional<DesktopMode> desktopModeOptional,
BroadcastDispatcher broadcastDispatcher,
- ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -587,7 +528,7 @@
}
mContext = context;
- mPipOptional = pipOptional;
+ mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
@@ -602,11 +543,6 @@
.supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
mSysUiState.addCallback(this::notifySystemUiStateFlags);
- mOneHandedOptional = oneHandedOptional;
- mShellTransitions = shellTransitions;
- mRecentTasks = recentTasks;
- mBackAnimation = backAnimation;
- mDesktopModeOptional = desktopModeOptional;
mUiEventLogger = uiEventLogger;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -636,9 +572,6 @@
});
mCommandQueue = commandQueue;
- mSplitScreenOptional = splitScreenOptional;
- mFloatingTasksOptional = floatingTasksOptional;
-
// Listen for user setup
startTracking();
@@ -647,7 +580,6 @@
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
- mStartingSurface = startingSurface;
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
// Listen for assistant changes
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index e0cd482..ba779c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -20,8 +20,8 @@
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -36,11 +36,11 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
- ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
- ViewIdToTranslate(R.id.rightLayout, RIGHT),
- ViewIdToTranslate(R.id.clock, LEFT),
- ViewIdToTranslate(R.id.date, LEFT)),
+ ViewIdToTranslate(R.id.quick_settings_panel, START),
+ ViewIdToTranslate(R.id.notification_stack_scroller, END),
+ ViewIdToTranslate(R.id.rightLayout, END),
+ ViewIdToTranslate(R.id.clock, START),
+ ViewIdToTranslate(R.id.date, START)),
progressProvider = progressProvider)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7254e09..1110386 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -425,8 +425,6 @@
new KeyguardClockPositionAlgorithm.Result();
private boolean mIsExpanding;
- private boolean mBlockTouches;
-
/**
* Determines if QS should be already expanded when expanding shade.
* Used for split shade, two finger gesture as well as accessibility shortcut to QS.
@@ -1693,7 +1691,6 @@
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
- mBlockTouches = false;
mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
@@ -4198,7 +4195,7 @@
"NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
+ "," + event.getY() + ")");
}
- if (mBlockTouches || mQs.disallowPanelTouches()) {
+ if (mQs.disallowPanelTouches()) {
return false;
}
initDownStates(event);
@@ -4241,8 +4238,7 @@
}
- if (mBlockTouches || (mQsFullyExpanded && mQs != null
- && mQs.disallowPanelTouches())) {
+ if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
return false;
}
@@ -4700,6 +4696,8 @@
if (!animatingUnlockedShadeToKeyguard) {
// Only make the status bar visible if we're not animating the screen off, since
// we only want to be showing the clock/notifications during the animation.
+ mShadeLog.v("Updating keyguard status bar state to "
+ + (keyguardShowing ? "visible" : "invisible"));
mKeyguardStatusBarViewController.updateViewState(
/* alpha= */ 1f,
keyguardShowing ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 4d53064..ce730ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -20,14 +20,16 @@
import android.widget.FrameLayout
/**
- * A temporary base class that's shared between our old status bar wifi view implementation
- * ([StatusBarWifiView]) and our new status bar wifi view implementation
- * ([ModernStatusBarWifiView]).
+ * A temporary base class that's shared between our old status bar connectivity view implementations
+ * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
+ * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
*
* Once our refactor is over, we should be able to delete this go-between class and the old view
* class.
*/
-abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+abstract class BaseStatusBarFrameLayout
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 8699441..c290ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -42,7 +42,6 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -97,6 +96,7 @@
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
+ private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -157,7 +157,7 @@
break;
case Intent.ACTION_USER_UNLOCKED:
// Start the overview connection to the launcher service
- Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
break;
case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
final IntentSender intentSender = intent.getParcelableExtra(
@@ -199,6 +199,7 @@
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
+ Lazy<OverviewProxyService> overviewProxyServiceLazy,
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
@@ -214,6 +215,7 @@
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
+ mOverviewProxyServiceLazy = overviewProxyServiceLazy;
statusBarStateController.addCallback(this);
mLockPatternUtils = new LockPatternUtils(context);
mKeyguardManager = keyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c900c5a..4be5a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -43,7 +43,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -89,11 +88,9 @@
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
- private final StatusBarStateController mStatusBarStateController
- = Dependency.get(StatusBarStateController.class);
- private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
+ private final StatusBarStateController mStatusBarStateController;
+ private final SysuiColorExtractor mColorExtractor;
+ private final KeyguardStateController mKeyguardStateController;
private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
@@ -179,6 +176,9 @@
NotifCollection notifCollection,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
+ StatusBarStateController statusBarStateController,
+ SysuiColorExtractor colorExtractor,
+ KeyguardStateController keyguardStateController,
DumpManager dumpManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
@@ -192,6 +192,9 @@
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
+ mStatusBarStateController = statusBarStateController;
+ mColorExtractor = colorExtractor;
+ mKeyguardStateController = keyguardStateController;
setupNotifPipeline();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 48c6e27..fdad101 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -29,7 +29,6 @@
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -43,7 +42,10 @@
import java.util.ArrayList;
-public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
+/**
+ * View group for the mobile icon in the status bar
+ */
+public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
StatusIconDisplayable {
private static final String TAG = "StatusBarMobileView";
@@ -101,11 +103,6 @@
super(context, attrs, defStyleAttr);
}
- public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
@Override
public void getDrawingRect(Rect outRect) {
super.getDrawingRect(outRect);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index f3e74d9..decc70d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -40,7 +40,7 @@
/**
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
*/
-public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
+public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
private static final String TAG = "StatusBarWifiView";
/// Used to show etc dots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7cd79ca..11e3d17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,6 +26,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -130,6 +131,9 @@
NotifCollection notifCollection,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
+ StatusBarStateController statusBarStateController,
+ SysuiColorExtractor colorExtractor,
+ KeyguardStateController keyguardStateController,
DumpManager dumpManager) {
return new NotificationMediaManager(
context,
@@ -142,6 +146,9 @@
notifCollection,
mainExecutor,
mediaDataManager,
+ statusBarStateController,
+ colorExtractor,
+ keyguardStateController,
dumpManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 1aa0295..8e646a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -19,6 +19,8 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.ForegroundServiceNotificationListener
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
@@ -36,6 +38,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -68,6 +71,8 @@
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
private val fgsNotifListener: ForegroundServiceNotificationListener,
+ private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
+ private val featureFlags: FeatureFlags
) : NotificationsController {
override fun initialize(
@@ -107,6 +112,9 @@
peopleSpaceWidgetManager.attach(notificationListener)
fgsNotifListener.init()
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
+ memoryMonitor.get().init()
+ }
}
// TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
new file mode 100644
index 0000000..832a739
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+/** Describes usage of a notification. */
+data class NotificationMemoryUsage(
+ val packageName: String,
+ val notificationId: String,
+ val objectUsage: NotificationObjectUsage,
+)
+
+/**
+ * Describes current memory usage of a [android.app.Notification] object.
+ *
+ * The values are in bytes.
+ */
+data class NotificationObjectUsage(
+ val smallIcon: Int,
+ val largeIcon: Int,
+ val extras: Int,
+ val style: String?,
+ val styleIcon: Int,
+ val bigPicture: Int,
+ val extender: Int,
+ val hasCustomView: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
new file mode 100644
index 0000000..958978e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -0,0 +1,243 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.core.util.contains
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** This class monitors and logs current Notification memory use. */
+@SysUISingleton
+class NotificationMemoryMonitor
+@Inject
+constructor(
+ val notificationPipeline: NotifPipeline,
+ val dumpManager: DumpManager,
+) : Dumpable {
+
+ companion object {
+ private const val TAG = "NotificationMemMonitor"
+ private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+ private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+ private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ }
+
+ fun init() {
+ Log.d(TAG, "NotificationMemoryMonitor initialized.")
+ dumpManager.registerDumpable(javaClass.simpleName, this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+ }
+
+ @WorkerThread
+ fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
+ return notificationMemoryUse(notificationPipeline.allNotifs)
+ }
+
+ /** Returns a list of memory use entries for currently shown notifications. */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notifications: Collection<NotificationEntry>
+ ): List<NotificationMemoryUsage> {
+ return notifications
+ .asSequence()
+ .map { entry ->
+ val packageName = entry.sbn.packageName
+ val notificationObjectUsage =
+ computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
+ NotificationMemoryUsage(
+ packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationObjectUsage
+ )
+ }
+ .toList()
+ }
+
+ /**
+ * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+ * inspect Bitmaps in the object and provide summary of memory usage.
+ */
+ private fun computeNotificationObjectUse(
+ notification: Notification,
+ seenBitmaps: HashSet<Int>
+ ): NotificationObjectUsage {
+ val extras = notification.extras
+ val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+ val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+ // Collect memory usage of extra styles
+
+ // Big Picture
+ val bigPictureIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+ val bigPictureUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+ // People
+ val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+ val peopleUse =
+ peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+ // Calling
+ val callingPersonUse =
+ computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+ val verificationIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+ // Messages
+ val messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+ )
+ val messagesUse =
+ messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+ val historicMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ )
+ val historyicMessagesUse =
+ historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+ // Extenders
+ val carExtender = extras.getBundle(CAR_EXTENSIONS)
+ val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+ val carExtenderIcon =
+ computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+ val tvExtender = extras.getBundle(TV_EXTENSIONS)
+ val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+ val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+ val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+ val wearExtenderBackground =
+ computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+ val style = notification.notificationStyle
+ val hasCustomView = notification.contentView != null || notification.bigContentView != null
+ val extrasSize = computeBundleSize(extras)
+
+ return NotificationObjectUsage(
+ smallIconUse,
+ largeIconUse,
+ extrasSize,
+ style?.simpleName,
+ bigPictureIconUse +
+ peopleUse +
+ callingPersonUse +
+ verificationIconUse +
+ messagesUse +
+ historyicMessagesUse,
+ bigPictureUse,
+ carExtenderSize +
+ carExtenderIcon +
+ tvExtenderSize +
+ wearExtenderSize +
+ wearExtenderBackground,
+ hasCustomView
+ )
+ }
+
+ /**
+ * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+ * bitmaps). Can be slow.
+ */
+ private fun computeBundleSize(extras: Bundle): Int {
+ val parcel = Parcel.obtain()
+ try {
+ extras.writeToParcel(parcel, 0)
+ return parcel.dataSize()
+ } finally {
+ parcel.recycle()
+ }
+ }
+
+ /**
+ * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+ * if the key does not exist in extras.
+ */
+ private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+ return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+ is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+ is Icon -> computeIconUse(parcelable, seenBitmaps)
+ is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+ else -> 0
+ }
+ }
+
+ /**
+ * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+ * defined via Uri or a resource.
+ *
+ * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+ */
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ when (icon?.type) {
+ Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+ else -> 0
+ }
+
+ /**
+ * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+ * seenBitmaps set, this method returns 0 to avoid double counting.
+ *
+ * @return memory usage of the bitmap in bytes
+ */
+ private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+ val refId = System.identityHashCode(bitmap)
+ if (seenBitmaps?.contains(refId) == true) {
+ return 0
+ }
+
+ seenBitmaps?.add(refId)
+ return bitmap.allocationByteCount
+ }
+
+ private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+ val refId = System.identityHashCode(icon.dataBytes)
+ if (seenBitmaps.contains(refId)) {
+ return 0
+ }
+
+ seenBitmaps.add(refId)
+ return icon.dataLength
+ }
+}
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 0b650d4..087dc71 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
@@ -1477,6 +1477,20 @@
}
}
+ /**
+ * Sets the alpha on the content, while leaving the background of the row itself as is.
+ *
+ * @param alpha alpha value to apply to the notification content
+ */
+ public void setContentAlpha(float alpha) {
+ for (NotificationContentView l : mLayouts) {
+ l.setAlpha(alpha);
+ }
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setAlpha(alpha);
+ }
+ }
+
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
mPrivateLayout.setIsLowPriority(isLowPriority);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index df81c0e..8de0365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1986,6 +1986,25 @@
public void setRemoteInputVisible(boolean remoteInputVisible) {
mRemoteInputVisible = remoteInputVisible;
setClipChildren(!remoteInputVisible);
+ setActionsImportanceForAccessibility(
+ remoteInputVisible ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+
+ private void setActionsImportanceForAccessibility(int mode) {
+ if (mExpandedChild != null) {
+ setActionsImportanceForAccessibility(mode, mExpandedChild);
+ }
+ if (mHeadsUpChild != null) {
+ setActionsImportanceForAccessibility(mode, mHeadsUpChild);
+ }
+ }
+
+ private void setActionsImportanceForAccessibility(int mode, View child) {
+ View actionsCandidate = child.findViewById(com.android.internal.R.id.actions);
+ if (actionsCandidate != null) {
+ actionsCandidate.setImportantForAccessibility(mode);
+ }
}
@Override
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 83705bc..836cacc 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
@@ -1297,7 +1297,9 @@
+ mAmbientState.getOverExpansion()
- getCurrentOverScrollAmount(false /* top */);
float fraction = mAmbientState.getExpansionFraction();
- if (mAmbientState.isBouncerInTransit()) {
+ // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
+ // overlap. Otherwise, we maintain the normal fraction for smoothness.
+ if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
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 843a9ff..5c09d61 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
@@ -56,12 +56,13 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
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.media.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -178,6 +179,7 @@
@Nullable private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
+ private final FeatureFlags mFeatureFlags;
private View mLongPressedView;
@@ -639,7 +641,8 @@
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
- NotificationStackSizeCalculator notificationStackSizeCalculator) {
+ NotificationStackSizeCalculator notificationStackSizeCalculator,
+ FeatureFlags featureFlags) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
@@ -675,6 +678,7 @@
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mShadeController = shadeController;
+ mFeatureFlags = featureFlags;
updateResources();
}
@@ -739,9 +743,7 @@
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
- mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly
- mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-
+ mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 2d2fbe5..ee57411 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -185,6 +185,13 @@
return false;
}
+ @Override
+ protected void updateSwipeProgressAlpha(View animView, float alpha) {
+ if (animView instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) animView).setContentAlpha(alpha);
+ }
+ }
+
@VisibleForTesting
protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 0b63bbf..f736047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -4453,10 +4453,11 @@
Trace.beginSection("CentralSurfaces#updateDozing");
mDozing = isDozing;
- // Collapse the notification panel if open
boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
&& mDozeParameters.shouldControlScreenOff();
- mNotificationPanelViewController.resetViews(dozingAnimated);
+ // resetting views is already done when going into doze, there's no need to
+ // reset them again when we're waking up
+ mNotificationPanelViewController.resetViews(dozingAnimated && isDozing);
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 54d39fd..de7b152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -93,13 +93,13 @@
private boolean mControlScreenOffAnimation;
private boolean mIsQuickPickupEnabled;
- private boolean mKeyguardShowing;
+ private boolean mKeyguardVisible;
@VisibleForTesting
final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ mKeyguardVisible = visible;
updateControlScreenOff();
}
@@ -293,7 +293,7 @@
public void updateControlScreenOff() {
if (!getDisplayNeedsBlanking()) {
final boolean controlScreenOff =
- getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+ getAlwaysOn() && (mKeyguardVisible || shouldControlUnlockedScreenOff());
setControlScreenOffAnimation(controlScreenOff);
}
}
@@ -348,7 +348,7 @@
}
private boolean willAnimateFromLockScreenToAod() {
- return getAlwaysOn() && mKeyguardShowing;
+ return getAlwaysOn() && mKeyguardVisible;
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 7de4668..0067316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.os.Handler;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -34,9 +33,6 @@
*/
@SysUISingleton
public class DozeScrimController implements StateListener {
- private static final String TAG = "DozeScrimController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
private final DozeLog mDozeLog;
private final DozeParameters mDozeParameters;
private final Handler mHandler = new Handler();
@@ -44,28 +40,26 @@
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private boolean mFullyPulsing;
private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
@Override
public void onDisplayBlanked() {
- if (DEBUG) {
- Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.reasonToString(mPulseReason));
- }
if (!mDozing) {
+ mDozeLog.tracePulseDropped("onDisplayBlanked - not dozing");
return;
}
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
+ if (mPulseCallback != null) {
+ // Signal that the pulse is ready to turn the screen on and draw.
+ mDozeLog.tracePulseStart(mPulseReason);
+ mPulseCallback.onPulseStarted();
+ }
}
@Override
public void onFinished() {
- if (DEBUG) {
- Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- }
+ mDozeLog.tracePulseEvent("scrimCallback-onFinished", mDozing, mPulseReason);
+
if (!mDozing) {
return;
}
@@ -78,7 +72,6 @@
mHandler.postDelayed(mPulseOutExtended,
mDozeParameters.getPulseVisibleDurationExtended());
}
- mFullyPulsing = true;
}
/**
@@ -118,19 +111,14 @@
}
if (!mDozing || mPulseCallback != null) {
- if (DEBUG) {
- Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
- + (mPulseCallback != null));
- }
// Pulse suppressed.
callback.onPulseFinished();
if (!mDozing) {
- mDozeLog.tracePulseDropped("device isn't dozing");
+ mDozeLog.tracePulseDropped("pulse - device isn't dozing");
} else {
- mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+ mDozeLog.tracePulseDropped("pulse - already has pulse callback mPulseCallback="
+ mPulseCallback);
}
-
return;
}
@@ -141,9 +129,7 @@
}
public void pulseOutNow() {
- if (mPulseCallback != null && mFullyPulsing) {
- mPulseOut.run();
- }
+ mPulseOut.run();
}
public boolean isPulsing() {
@@ -168,24 +154,16 @@
private void cancelPulsing() {
if (mPulseCallback != null) {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
- mFullyPulsing = false;
+ mDozeLog.tracePulseEvent("cancel", mDozing, mPulseReason);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
}
}
- private void pulseStarted() {
- mDozeLog.tracePulseStart(mPulseReason);
- if (mPulseCallback != null) {
- mPulseCallback.onPulseStarted();
- }
- }
-
private void pulseFinished() {
- mDozeLog.tracePulseFinish();
if (mPulseCallback != null) {
+ mDozeLog.tracePulseFinish();
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
@@ -202,10 +180,9 @@
private final Runnable mPulseOut = new Runnable() {
@Override
public void run() {
- mFullyPulsing = false;
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
- if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
+ mDozeLog.tracePulseEvent("out", mDozing, mPulseReason);
if (!mDozing) return;
pulseFinished();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 24ce5e9..5196e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -36,7 +36,6 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -48,6 +47,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
@@ -80,7 +80,6 @@
private final BatteryController mBatteryController;
private final ScrimController mScrimController;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private final KeyguardViewMediator mKeyguardViewMediator;
private final Lazy<AssistManager> mAssistManagerLazy;
private final DozeScrimController mDozeScrimController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -95,6 +94,7 @@
private View mAmbientIndicationContainer;
private CentralSurfaces mCentralSurfaces;
private boolean mAlwaysOnSuppressed;
+ private boolean mPulsePending;
@Inject
public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -104,7 +104,6 @@
HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
ScrimController scrimController,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
- KeyguardViewMediator keyguardViewMediator,
Lazy<AssistManager> assistManagerLazy,
DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
PulseExpansionHandler pulseExpansionHandler,
@@ -122,7 +121,6 @@
mBatteryController = batteryController;
mScrimController = scrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
- mKeyguardViewMediator = keyguardViewMediator;
mAssistManagerLazy = assistManagerLazy;
mDozeScrimController = dozeScrimController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -131,6 +129,7 @@
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
+ mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
}
// TODO: we should try to not pass status bar in here if we can avoid it.
@@ -246,7 +245,7 @@
mDozeScrimController.pulse(new PulseCallback() {
@Override
public void onPulseStarted() {
- callback.onPulseStarted();
+ callback.onPulseStarted(); // requestState(DozeMachine.State.DOZE_PULSING)
mCentralSurfaces.updateNotificationPanelTouchState();
setPulsing(true);
}
@@ -254,7 +253,7 @@
@Override
public void onPulseFinished() {
mPulsing = false;
- callback.onPulseFinished();
+ callback.onPulseFinished(); // requestState(DozeMachine.State.DOZE_PULSE_DONE)
mCentralSurfaces.updateNotificationPanelTouchState();
mScrimController.setWakeLockScreenSensorActive(false);
setPulsing(false);
@@ -338,9 +337,8 @@
@Override
public void stopPulsing() {
- if (mDozeScrimController.isPulsing()) {
- mDozeScrimController.pulseOutNow();
- }
+ setPulsePending(false); // prevent any pending pulses from continuing
+ mDozeScrimController.pulseOutNow();
}
@Override
@@ -451,6 +449,16 @@
}
}
+ @Override
+ public boolean isPulsePending() {
+ return mPulsePending;
+ }
+
+ @Override
+ public void setPulsePending(boolean isPulsePending) {
+ mPulsePending = isPulsePending;
+ }
+
/**
* Whether always-on-display is being suppressed. This does not affect wakeup gestures like
* pickup and tap.
@@ -458,4 +466,22 @@
public boolean isAlwaysOnSuppressed() {
return mAlwaysOnSuppressed;
}
+
+ final OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+ if (mStatusBarStateController.isDozing() && isHeadsUp) {
+ entry.setPulseSuppressed(false);
+ fireNotificationPulse(entry);
+ if (isPulsing()) {
+ mDozeScrimController.cancelPendingPulseTimeout();
+ }
+ }
+ if (!isHeadsUp && !mHeadsUpManagerPhone.hasNotifications()) {
+ // There are no longer any notifications to show. We should end the
+ // pulse now.
+ stopPulsing();
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index b58dbe2..e3e8572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -88,7 +88,7 @@
updateListeningState()
}
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
updateListeningState()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 0026b71..14cebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -40,6 +40,7 @@
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterViewController;
@@ -116,6 +117,7 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
private final Object mLock = new Object();
+ private final KeyguardLogger mLogger;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -185,8 +187,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (visible) {
updateUserSwitcher();
}
}
@@ -279,7 +281,8 @@
StatusBarUserInfoTracker statusBarUserInfoTracker,
SecureSettings secureSettings,
CommandQueue commandQueue,
- @Main Executor mainExecutor
+ @Main Executor mainExecutor,
+ KeyguardLogger logger
) {
super(view);
mCarrierTextController = carrierTextController;
@@ -304,6 +307,7 @@
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
+ mLogger = logger;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mKeyguardStateController.addCallback(
@@ -430,6 +434,7 @@
/** Animate the keyguard status bar in. */
public void animateKeyguardStatusBarIn() {
+ mLogger.d("animating status bar in");
if (mDisableStateTracker.isDisabled()) {
// If our view is disabled, don't allow us to animate in.
return;
@@ -445,6 +450,7 @@
/** Animate the keyguard status bar out. */
public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+ mLogger.d("animating status bar out");
ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
anim.addUpdateListener(mAnimatorUpdateListener);
anim.setStartDelay(startDelay);
@@ -481,6 +487,9 @@
newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
* mKeyguardStatusBarAnimateAlpha
* (1.0f - mKeyguardHeadsUpShowingAmount);
+ if (newAlpha != mView.getAlpha() && (newAlpha == 0 || newAlpha == 1)) {
+ mLogger.logStatusBarCalculatedAlpha(newAlpha);
+ }
}
boolean hideForBypass =
@@ -503,6 +512,10 @@
if (mDisableStateTracker.isDisabled()) {
visibility = View.INVISIBLE;
}
+ if (visibility != mView.getVisibility()) {
+ mLogger.logStatusBarAlphaVisibility(visibility, alpha,
+ StatusBarState.toString(mStatusBarState));
+ }
mView.setAlpha(alpha);
mView.setVisibility(visibility);
}
@@ -596,6 +609,8 @@
pw.println("KeyguardStatusBarView:");
pw.println(" mBatteryListening: " + mBatteryListening);
pw.println(" mExplicitAlpha: " + mExplicitAlpha);
+ pw.println(" alpha: " + mView.getAlpha());
+ pw.println(" visibility: " + mView.getVisibility());
mView.dump(pw, args);
}
@@ -605,6 +620,10 @@
* @param alpha a value between 0 and 1. -1 if the value is to be reset/ignored.
*/
public void setAlpha(float alpha) {
+ if (mExplicitAlpha != alpha && (mExplicitAlpha == -1 || alpha == -1)) {
+ // logged if value changed to ignored or from ignored
+ mLogger.logStatusBarExplicitAlpha(alpha);
+ }
mExplicitAlpha = alpha;
updateViewState();
}
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 4d1c361..9f93223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1534,7 +1534,7 @@
private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index ae201e3..5512bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,8 +21,6 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -41,9 +39,6 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarStateController mStatusBarStateController;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
- private final NotificationsController mNotificationsController;
- private final DozeServiceHost mDozeServiceHost;
- private final DozeScrimController mDozeScrimController;
@Inject
StatusBarHeadsUpChangeListener(
@@ -53,10 +48,7 @@
KeyguardBypassController keyguardBypassController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController statusBarStateController,
- NotificationRemoteInputManager notificationRemoteInputManager,
- NotificationsController notificationsController,
- DozeServiceHost dozeServiceHost,
- DozeScrimController dozeScrimController) {
+ NotificationRemoteInputManager notificationRemoteInputManager) {
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
@@ -65,9 +57,6 @@
mHeadsUpManager = headsUpManager;
mStatusBarStateController = statusBarStateController;
mNotificationRemoteInputManager = notificationRemoteInputManager;
- mNotificationsController = notificationsController;
- mDozeServiceHost = dozeServiceHost;
- mDozeScrimController = dozeScrimController;
}
@Override
@@ -117,20 +106,4 @@
}
}
}
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- if (mStatusBarStateController.isDozing() && isHeadsUp) {
- entry.setPulseSuppressed(false);
- mDozeServiceHost.fireNotificationPulse(entry);
- if (mDozeServiceHost.isPulsing()) {
- mDozeScrimController.cancelPendingPulseTimeout();
- }
- }
- if (!isHeadsUp && !mHeadsUpManager.hasNotifications()) {
- // There are no longer any notifications to show. We should end the
- //pulse now.
- mDozeScrimController.pulseOutNow();
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d6d021f..ece7ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import android.annotation.Nullable;
@@ -38,7 +39,7 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.BaseStatusBarWifiView;
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,6 +49,10 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.util.Assert;
@@ -84,6 +89,12 @@
void setMobileIcons(String slot, List<MobileIconState> states);
/**
+ * This method completely replaces {@link #setMobileIcons} with the information from the new
+ * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
+ * to worry about funneling MobileIconState objects through anymore.
+ */
+ void setNewMobileIconSubIds(List<Integer> subIds);
+ /**
* Display the no calling & SMS icons.
*/
void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
@@ -141,12 +152,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -207,6 +220,7 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
@@ -214,10 +228,12 @@
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
+ MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
+ mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
}
@@ -227,6 +243,7 @@
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -244,11 +261,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
}
@@ -284,14 +304,18 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
+ mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -301,6 +325,7 @@
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider);
}
}
@@ -315,6 +340,8 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileIconsViewModel mMobileIconsViewModel;
+
protected final Context mContext;
protected final int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -333,7 +360,9 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mGroup = group;
mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
@@ -342,6 +371,14 @@
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+
+ if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ // This starts the flow for the new pipeline, and will notify us of changes
+ mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+ MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+ } else {
+ mMobileIconsViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -394,6 +431,9 @@
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
+
+ case TYPE_MOBILE_NEW:
+ return addNewMobileIcon(index, slot, holder.getTag());
}
return null;
@@ -410,7 +450,7 @@
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarWifiView view;
+ final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
view = onCreateModernStatusBarWifiView(slot);
// When [ModernStatusBarWifiView] is created, it will automatically apply the
@@ -429,17 +469,47 @@
}
@VisibleForTesting
- protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+ protected StatusIconDisplayable addMobileIcon(
+ int index,
+ String slot,
+ MobileIconState state
+ ) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "pipeline is enabled is not supported");
+ }
+
// Use the `subId` field as a key to query for the correct context
- StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
- view.applyMobileState(state);
- mGroup.addView(view, index, onCreateLayoutParams());
+ StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
+ mobileView.applyMobileState(state);
+ mGroup.addView(mobileView, index, onCreateLayoutParams());
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(state.subId, mContext);
mDemoStatusIcons.addMobileView(state, mobileContext);
}
+ return mobileView;
+ }
+
+ protected StatusIconDisplayable addNewMobileIcon(
+ int index,
+ String slot,
+ int subId
+ ) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
+ mGroup.addView(view, index, onCreateLayoutParams());
+
+ if (mIsInDemoMode) {
+ // TODO (b/249790009): demo mode should be handled at the data layer in the
+ // new pipeline
+ }
+
return view;
}
@@ -464,6 +534,15 @@
return view;
}
+ private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
+ String slot, int subId) {
+ return ModernStatusBarMobileView
+ .constructAndBind(
+ mContext,
+ slot,
+ mMobileIconsViewModel.viewModelForSub(subId));
+ }
+
protected LinearLayout.LayoutParams onCreateLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
@@ -519,6 +598,10 @@
return;
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
+ return;
+ case TYPE_MOBILE_NEW:
+ // Nothing, the icon updates itself now
+ return;
default:
break;
}
@@ -542,9 +625,13 @@
}
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
- StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
- if (view != null) {
- view.applyMobileState(state);
+ View view = mGroup.getChildAt(viewIndex);
+ if (view instanceof StatusBarMobileView) {
+ ((StatusBarMobileView) view).applyMobileState(state);
+ } else {
+ // ModernStatusBarMobileView automatically updates via the ViewModel
+ throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+ + "the new pipeline");
}
if (mIsInDemoMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 7c31366..e106b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
@@ -66,8 +67,8 @@
private final StatusBarIconList mStatusBarIconList;
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconHideList = new ArraySet<>();
-
- private Context mContext;
+ private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+ private final Context mContext;
/** */
@Inject
@@ -78,9 +79,12 @@
ConfigurationController configurationController,
TunerService tunerService,
DumpManager dumpManager,
- StatusBarIconList statusBarIconList) {
+ StatusBarIconList statusBarIconList,
+ StatusBarPipelineFlags statusBarPipelineFlags
+ ) {
mStatusBarIconList = statusBarIconList;
mContext = context;
+ mStatusBarPipelineFlags = statusBarPipelineFlags;
configurationController.addCallback(this);
commandQueue.addCallback(this);
@@ -220,6 +224,11 @@
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ + "pipeline frontend is enabled");
+ return;
+ }
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
@@ -227,7 +236,6 @@
Collections.reverse(iconStates);
for (MobileIconState state : iconStates) {
-
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -239,6 +247,28 @@
}
}
+ @Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring new pipeline callback, "
+ + "since the frontend is disabled");
+ return;
+ }
+ Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+
+ Collections.reverse(subIds);
+
+ for (Integer subId : subIds) {
+ StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
+ if (holder == null) {
+ holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
+ setIcon("mobile", holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+ }
+
/**
* Accept a list of CallIndicatorIconStates, and show the call strength icons.
* @param slot statusbar slot for the call strength icons
@@ -384,8 +414,6 @@
}
}
-
-
private void handleSet(String slotName, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd..68a203e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -25,6 +26,10 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
@@ -33,15 +38,35 @@
public static final int TYPE_ICON = 0;
public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place
+ * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
+ * to inform the old view system about changes to the data set (the list of mobile icons). The
+ * design of the new pipeline should allow for removal of this icon holder type, and obsolete
+ * the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_NEW = 3;
+
+ @IntDef({
+ TYPE_ICON,
+ TYPE_WIFI,
+ TYPE_MOBILE,
+ TYPE_MOBILE_NEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface IconType {}
private StatusBarIcon mIcon;
private WifiIconState mWifiState;
private MobileIconState mMobileState;
- private int mType = TYPE_ICON;
+ private @IconType int mType = TYPE_ICON;
private int mTag = 0;
private StatusBarIconHolder() {
-
}
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
@@ -80,6 +105,18 @@
}
/**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_MOBILE_NEW;
+ holder.mTag = subId;
+
+ return holder;
+ }
+
+ /**
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
@@ -95,7 +132,7 @@
return holder;
}
- public int getType() {
+ public @IconType int getType() {
return mType;
}
@@ -134,8 +171,12 @@
return mWifiState.visible;
case TYPE_MOBILE:
return mMobileState.visible;
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ return true;
- default: return true;
+ default:
+ return true;
}
}
@@ -156,6 +197,10 @@
case TYPE_MOBILE:
mMobileState.visible = visible;
break;
+
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ break;
}
}
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 a0578fa..dfb7aab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -405,8 +405,9 @@
} else if (mNotificationPanelViewController.isUnlockHintRunning()) {
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
@@ -414,8 +415,9 @@
} else if (bouncerNeedsScrimming()) {
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else if (mShowing && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
@@ -423,8 +425,9 @@
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
+ } else {
+ mBouncerInteractor.setExpansion(fraction);
}
- mBouncerInteractor.setExpansion(fraction);
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
@@ -432,16 +435,18 @@
&& !bouncerIsAnimatingAway()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+ } else {
+ mBouncerInteractor.show(/* isScrimmed= */false);
}
- mBouncerInteractor.show(/* isScrimmed= */false);
}
} else if (!mShowing && isBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -487,8 +492,9 @@
mCentralSurfaces.hideKeyguard();
if (mBouncer != null) {
mBouncer.show(true /* resetSecuritySelection */);
+ } else {
+ mBouncerInteractor.show(true);
}
- mBouncerInteractor.show(true);
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
@@ -529,8 +535,9 @@
void hideBouncer(boolean destroyView) {
if (mBouncer != null) {
mBouncer.hide(destroyView);
+ } else {
+ mBouncerInteractor.hide();
}
- mBouncerInteractor.hide();
if (mShowing) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
@@ -551,8 +558,9 @@
if (mShowing && !isBouncerShowing()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+ } else {
+ mBouncerInteractor.show(scrimmed);
}
- mBouncerInteractor.show(scrimmed);
}
updateStates();
}
@@ -588,9 +596,10 @@
if (mBouncer != null) {
mBouncer.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
+ } else {
+ mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+ mKeyguardGoneCancelAction);
}
- mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
}
@@ -603,17 +612,21 @@
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- mBouncerInteractor.show(/* isScrimmed= */true);
- if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
+ if (mBouncer != null) {
+ mBouncer.show(false /* resetSecuritySelection */);
+ } else {
+ mBouncerInteractor.show(/* isScrimmed= */true);
+ }
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
- mBouncerInteractor.setDismissAction(
- mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
- mBouncerInteractor.show(/* isScrimmed= */true);
if (mBouncer != null) {
mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
+ } else {
+ mBouncerInteractor.setDismissAction(
+ mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+ mBouncerInteractor.show(/* isScrimmed= */true);
}
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
@@ -717,8 +730,9 @@
public void onFinishedGoingToSleep() {
if (mBouncer != null) {
mBouncer.onScreenTurnedOff();
+ } else {
+ mBouncerInteractor.onScreenTurnedOff();
}
- mBouncerInteractor.onScreenTurnedOff();
}
@Override
@@ -733,11 +747,6 @@
if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
reset(dozing /* hideBouncerWhenShowing */);
}
-
- if (bouncerIsOrWillBeShowing()) {
- // Ensure bouncer is not shown when dozing state changes.
- hideBouncer(false);
- }
updateStates();
if (!dozing) {
@@ -835,8 +844,9 @@
if (bouncerIsShowing()) {
if (mBouncer != null) {
mBouncer.startPreHideAnimation(finishRunnable);
+ } else {
+ mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mBouncerInteractor.startDisappearAnimation(finishRunnable);
mCentralSurfaces.onBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
@@ -1107,13 +1117,15 @@
if (bouncerDismissible || !showing || remoteInputActive) {
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(true);
+ } else {
+ mBouncerInteractor.setBackButtonEnabled(true);
}
- mBouncerInteractor.setBackButtonEnabled(true);
} else {
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(false);
+ } else {
+ mBouncerInteractor.setBackButtonEnabled(false);
}
- mBouncerInteractor.setBackButtonEnabled(false);
}
}
@@ -1131,8 +1143,8 @@
if (occluded != mLastOccluded || mFirstUpdate) {
mKeyguardStateController.notifyKeyguardState(showing, occluded);
}
- if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
- mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
+ if (occluded != mLastOccluded || mShowing != showing || mFirstUpdate) {
+ mKeyguardUpdateManager.setKeyguardShowing(showing, occluded);
}
if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
|| bouncerShowing != mLastBouncerShowing) {
@@ -1279,8 +1291,9 @@
public void notifyKeyguardAuthenticated(boolean strongAuth) {
if (mBouncer != null) {
mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ } else {
+ mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
resetAlternateAuth(false);
@@ -1297,8 +1310,9 @@
} else {
if (mBouncer != null) {
mBouncer.showMessage(message, colorState);
+ } else {
+ mBouncerInteractor.showMessage(message, colorState);
}
- mBouncerInteractor.showMessage(message, colorState);
}
}
@@ -1345,8 +1359,9 @@
public void updateResources() {
if (mBouncer != null) {
mBouncer.updateResources();
+ } else {
+ mBouncerInteractor.updateResources();
}
- mBouncerInteractor.updateResources();
}
public void dump(PrintWriter pw) {
@@ -1431,9 +1446,9 @@
public void updateKeyguardPosition(float x) {
if (mBouncer != null) {
mBouncer.updateKeyguardPosition(x);
+ } else {
+ mBouncerInteractor.setKeyguardPosition(x);
}
-
- mBouncerInteractor.setKeyguardPosition(x);
}
private static class DismissWithActionRequest {
@@ -1475,9 +1490,9 @@
public boolean isBouncerInTransit() {
if (mBouncer != null) {
return mBouncer.inTransit();
+ } else {
+ return mBouncerInteractor.isInTransit();
}
-
- return mBouncerInteractor.isInTransit();
}
/**
@@ -1486,9 +1501,9 @@
public boolean bouncerIsShowing() {
if (mBouncer != null) {
return mBouncer.isShowing();
+ } else {
+ return mBouncerInteractor.isFullyShowing();
}
-
- return mBouncerInteractor.isFullyShowing();
}
/**
@@ -1497,9 +1512,9 @@
public boolean bouncerIsScrimmed() {
if (mBouncer != null) {
return mBouncer.isScrimmed();
+ } else {
+ return mBouncerInteractor.isScrimmed();
}
-
- return mBouncerInteractor.isScrimmed();
}
/**
@@ -1508,9 +1523,10 @@
public boolean bouncerIsAnimatingAway() {
if (mBouncer != null) {
return mBouncer.isAnimatingAway();
+ } else {
+ return mBouncerInteractor.isAnimatingAway();
}
- return mBouncerInteractor.isAnimatingAway();
}
/**
@@ -1519,9 +1535,9 @@
public boolean bouncerWillDismissWithAction() {
if (mBouncer != null) {
return mBouncer.willDismissWithAction();
+ } else {
+ return mBouncerInteractor.willDismissWithAction();
}
-
- return mBouncerInteractor.willDismissWithAction();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 9a7c3fa..06d5542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -30,4 +34,12 @@
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+ @Binds
+ abstract fun mobileSubscriptionRepository(
+ impl: MobileSubscriptionRepositoryImpl
+ ): MobileSubscriptionRepository
+
+ @Binds
+ abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
new file mode 100644
index 0000000..46ccf32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.Annotation.DataActivityType
+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
+
+/**
+ * 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 MobileSubscriptionModel(
+ /** From [ServiceStateListener.onServiceStateChanged] */
+ val isEmergencyOnly: Boolean = false,
+
+ /** 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,
+
+ /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
+ val dataConnectionState: Int? = null,
+
+ /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
+ @DataActivityType val dataActivityDirection: Int? = null,
+
+ /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+ val carrierNetworkChangeActive: Boolean? = null,
+
+ /** From [DisplayInfoListener.onDisplayInfoChanged] */
+ val displayInfo: TelephonyDisplayInfo? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
new file mode 100644
index 0000000..36de2a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+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.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileSubscriptionRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Get or create an observable for the given subscription ID */
+ fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileSubscriptionRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+) : MobileSubscriptionRepository {
+ private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ /**
+ * Each mobile subscription needs its own flow, which comes from registering listeners on the
+ * system. Use this method to create those flows and cache them for reuse
+ */
+ override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
+ return subIdFlowCache[subId]
+ ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+ }
+
+ @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
+
+ private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+ var state = MobileSubscriptionModel()
+ conflatedCallbackFlow {
+ val phony = telephonyManager.createForSubscriptionId(subId)
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ ServiceStateListener,
+ SignalStrengthsListener,
+ DataConnectionStateListener,
+ DataActivityListener,
+ CarrierNetworkListener,
+ DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+ override fun onSignalStrengthsChanged(signalStrength: 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
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state = state.copy(dataConnectionState = dataState)
+ trySend(state)
+ }
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+ state = state.copy(displayInfo = telephonyDisplayInfo)
+ trySend(state)
+ }
+ }
+ phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose {
+ phony.unregisterTelephonyCallback(callback)
+ // Release the cached flow
+ subIdFlowCache.remove(subId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
new file mode 100644
index 0000000..77de849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
+ * can change some policy related to display
+ */
+interface UserSetupRepository {
+ /** Observable tracking [DeviceProvisionedController.isUserSetup] */
+ val isUserSetupFlow: Flow<Boolean>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+ private val deviceProvisionedController: DeviceProvisionedController,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application scope: CoroutineScope,
+) : UserSetupRepository {
+ /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
+ override val isUserSetupFlow: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onUserSetupChanged() {
+ trySend(Unit)
+ }
+ }
+
+ deviceProvisionedController.addCallback(callback)
+
+ awaitClose { deviceProvisionedController.removeCallback(callback) }
+ }
+ .onStart { emit(Unit) }
+ .mapLatest { fetchUserSetupState() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private suspend fun fetchUserSetupState(): Boolean =
+ withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
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
new file mode 100644
index 0000000..40fe0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.CarrierConfigTracker
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+interface MobileIconInteractor {
+ /** Identifier for RAT type indicator */
+ val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** True if this line of service is emergency-only */
+ val isEmergencyOnly: Flow<Boolean>
+ /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
+ val level: Flow<Int>
+ /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
+ val numberOfLevels: Flow<Int>
+ /** True when we want to draw an icon that makes room for the exclamation mark */
+ val cutOut: Flow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+class MobileIconInteractorImpl(
+ mobileStatusInfo: Flow<MobileSubscriptionModel>,
+) : MobileIconInteractor {
+ override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
+
+ override val level: Flow<Int> =
+ mobileStatusInfo.map { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
+ }
+ }
+
+ /**
+ * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+ * once it's wired up inside of [CarrierConfigTracker]
+ */
+ override val numberOfLevels: Flow<Int> = flowOf(4)
+
+ /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
+ // TODO: find a better name for this?
+ override val cutOut: Flow<Boolean> = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
new file mode 100644
index 0000000..8e67e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Business layer logic for mobile subscription icons
+ *
+ * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
+ * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ */
+@SysUISingleton
+class MobileIconsInteractor
+@Inject
+constructor(
+ private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val carrierConfigTracker: CarrierConfigTracker,
+ userSetupRepo: UserSetupRepository,
+) {
+ private val activeMobileDataSubscriptionId =
+ mobileSubscriptionRepo.activeMobileDataSubscriptionId
+
+ private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ mobileSubscriptionRepo.subscriptionsFlow
+
+ /**
+ * Generally, SystemUI wants to show iconography for each subscription that is listed by
+ * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+ * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+ *
+ * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+ * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+ *
+ * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+ * subscriptions based on
+ * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+ * and by checking which subscription is opportunistic, or which one is active.
+ */
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
+ ->
+ // Based on the old logic,
+ if (unfilteredSubs.size != 2) {
+ return@combine unfilteredSubs
+ }
+
+ val info1 = unfilteredSubs[0]
+ val info2 = unfilteredSubs[1]
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return@combine unfilteredSubs
+ }
+
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+ } else {
+ return@combine if (info1.subscriptionId == activeId) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
+ }
+ }
+
+ val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+ /** Vends out new [MobileIconInteractor] for a particular subId */
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
+
+ /**
+ * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+ */
+ private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
+ mobileSubscriptionRepo.getFlowForSubId(subId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
new file mode 100644
index 0000000..380017c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This class is intended to provide a context to collect on the
+ * [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
+ * control [StatusBarIconController] to keep the old UI in sync with the new data source.
+ *
+ * It also provides a mechanism to create a top-level view model for each IconManager to know about
+ * the list of available mobile lines of service for which we want to show icons.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileUiAdapter
+@Inject
+constructor(
+ interactor: MobileIconsInteractor,
+ private val iconController: StatusBarIconController,
+ private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ @Application scope: CoroutineScope,
+) {
+ private val mobileSubIds: Flow<List<Int>> =
+ interactor.filteredSubscriptions.mapLatest { infos ->
+ infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+ }
+
+ /**
+ * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
+ * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
+ * house the mobile infos.
+ *
+ * NOTE: this should go away as the view presenter learns more about this data pipeline
+ */
+ private val mobileSubIdsState: StateFlow<List<Int>> =
+ mobileSubIds
+ .onEach {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ /**
+ * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
+ * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
+ * the old view system.
+ */
+ fun createMobileIconsViewModel(): MobileIconsViewModel =
+ iconsViewModelFactory.create(mobileSubIdsState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
new file mode 100644
index 0000000..1405b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+object MobileIconBinder {
+ /** Binds the view to the view-model, continuing to update the former based on the latter */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: MobileIconViewModel,
+ ) {
+ val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
+ val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+
+ view.isVisible = true
+ iconView.isVisible = true
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Set the icon for the triangle
+ launch {
+ viewModel.iconId.distinctUntilChanged().collect { iconId ->
+ mobileDrawable.level = iconId
+ }
+ }
+
+ // Set the tint
+ launch {
+ viewModel.tint.collect { tint ->
+ iconView.imageTintList = ColorStateList.valueOf(tint)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
new file mode 100644
index 0000000..e7d5ee2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+object MobileIconsBinder {
+ /**
+ * Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
+ * which is passed in and managed by [IconManager]. Once the subscription list flow starts
+ * collecting, [MobileUiAdapter] will send updates to the icon manager.
+ */
+ @JvmStatic
+ fun bind(view: View, viewModel: MobileIconsViewModel) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.subscriptionIdsFlow.collect {
+ // TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
+ // sets up a side-effect in this flow to trigger the methods on
+ // [StatusBarIconController] which allows for this pipeline to be a data
+ // source for the mobile icons.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
new file mode 100644
index 0000000..ec4fa9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import java.util.ArrayList
+
+class ModernStatusBarMobileView(
+ context: Context,
+ attrs: AttributeSet?,
+) : BaseStatusBarFrameLayout(context, attrs) {
+
+ private lateinit var slot: String
+ override fun getSlot() = slot
+
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ // TODO
+ }
+
+ override fun setStaticDrawableColor(color: Int) {
+ // TODO
+ }
+
+ override fun setDecorColor(color: Int) {
+ // TODO
+ }
+
+ override fun setVisibleState(state: Int, animate: Boolean) {
+ // TODO
+ }
+
+ override fun getVisibleState(): Int {
+ return STATE_ICON
+ }
+
+ override fun isIconVisible(): Boolean {
+ return true
+ }
+
+ companion object {
+
+ /**
+ * Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
+ * returns it.
+ */
+ @JvmStatic
+ fun constructAndBind(
+ context: Context,
+ slot: String,
+ viewModel: MobileIconViewModel,
+ ): ModernStatusBarMobileView {
+ return (LayoutInflater.from(context)
+ .inflate(R.layout.status_bar_mobile_signal_group_new, null)
+ as ModernStatusBarMobileView)
+ .also {
+ it.slot = slot
+ MobileIconBinder.bind(it, viewModel)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
new file mode 100644
index 0000000..cfabeba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions]
+ *
+ * TODO: figure out where carrier merged and VCN models go (probably here?)
+ */
+class MobileIconViewModel
+constructor(
+ val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ logger: ConnectivityPipelineLogger,
+) {
+ /** An int consumable by [SignalDrawable] for display */
+ var iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ level,
+ numberOfLevels,
+ cutOut ->
+ SignalDrawable.getState(level, numberOfLevels, cutOut)
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "iconId($subscriptionId)")
+
+ var tint: Flow<Int> = flowOf(Color.CYAN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
new file mode 100644
index 0000000..24c1db9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(InternalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView]
+ */
+class MobileIconsViewModel
+@Inject
+constructor(
+ val subscriptionIdsFlow: Flow<List<Int>>,
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+) {
+ /** TODO: do we need to cache these? */
+ fun viewModelForSub(subId: Int): MobileIconViewModel =
+ MobileIconViewModel(
+ subId,
+ interactor.createMobileConnectionInteractorForSubId(subId),
+ logger
+ )
+
+ class Factory
+ @Inject
+ constructor(
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+ ) {
+ fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+ return MobileIconsViewModel(
+ subscriptionIdsFlow,
+ interactor,
+ logger,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 6c616ac..0cd9bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,7 +22,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarWifiView
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -37,7 +37,7 @@
class ModernStatusBarWifiView(
context: Context,
attrs: AttributeSet?
-) : BaseStatusBarWifiView(context, attrs) {
+) : BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
private lateinit var binding: WifiViewBinder.Binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f4d08e0..437d4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -435,7 +435,7 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 712953e..494a4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -91,11 +91,11 @@
private final KeyguardUpdateMonitorCallback mInfoCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", visible));
// Any time the keyguard is hidden, try to close the user switcher menu to
// restore keyguard to the default state
- if (!showing) {
+ if (!visible) {
closeSwitcherIfOpenAndNotSimple(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5a33603..da6d455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -221,8 +221,10 @@
mEditText.setTextColor(textColor);
mEditText.setHintTextColor(hintColor);
- mEditText.getTextCursorDrawable().setColorFilter(
- accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+ if (mEditText.getTextCursorDrawable() != null) {
+ mEditText.getTextCursorDrawable().setColorFilter(
+ accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+ }
mContentBackground.setColor(editBgColor);
mContentBackground.setStroke(stroke, accentColor);
mDelete.setImageTintList(ColorStateList.valueOf(deleteFgColor));
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a52e2af..91e20ee 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -167,11 +167,19 @@
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
- open fun removeView(removalReason: String) {
- if (view == null) { return }
+ fun removeView(removalReason: String) {
+ if (shouldIgnoreViewRemoval(removalReason)) {
+ return
+ }
+ val currentView = view ?: return
+
+ animateViewOut(currentView) { windowManager.removeView(currentView) }
+
logger.logChipRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
- windowManager.removeView(view)
+ // Re-set the view to null immediately (instead as part of the animation end runnable) so
+ // that if a new view event comes in while this view is animating out, we still display the
+ // new view appropriately.
view = null
info = null
// No need to time the view out since it's already gone
@@ -179,6 +187,13 @@
}
/**
+ * Returns true if a view removal request should be ignored and false otherwise.
+ *
+ * Allows subclasses to keep the view visible for longer in certain circumstances.
+ */
+ open fun shouldIgnoreViewRemoval(removalReason: String): Boolean = false
+
+ /**
* A method implemented by subclasses to update [currentView] based on [newInfo].
*/
@CallSuper
@@ -190,7 +205,17 @@
* A method that can be implemented by subclasses to do custom animations for when the view
* appears.
*/
- open fun animateViewIn(view: ViewGroup) {}
+ internal open fun animateViewIn(view: ViewGroup) {}
+
+ /**
+ * A method that can be implemented by subclasses to do custom animations for when the view
+ * disappears.
+ *
+ * @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
+ */
+ internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ onAnimationEnd.run()
+ }
}
object TemporaryDisplayRemovalReason {
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 5f7d745..a925e38 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -67,6 +67,8 @@
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
private boolean mDefaultShowOperatorNameConfigLoaded;
private boolean mDefaultShowOperatorNameConfig;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
@Inject
public CarrierConfigTracker(
@@ -207,6 +209,22 @@
}
/**
+ * Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
+ * the default carrier config.
+ */
+ public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
+ if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
+ .getDefaultConfig().getBoolean(CarrierConfigManager
+ .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
+ );
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
+ }
+
+ return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
+ }
+
+ /**
* Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
* default value if no override exists
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 8c736dc..81ae6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.BroadcastRunning;
import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
@@ -51,6 +52,17 @@
return thread.getLooper();
}
+ /** BroadcastRunning Looper (for sending and receiving broadcasts) */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ public static Looper provideBroadcastRunningLooper() {
+ HandlerThread thread = new HandlerThread("BroadcastRunning",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ }
+
/** Long running tasks Looper */
@Provides
@SysUISingleton
@@ -83,7 +95,17 @@
}
/**
- * Provide a Long running Executor by default.
+ * Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
+ */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) {
+ return new ExecutorImpl(looper);
+ }
+
+ /**
+ * Provide a Long running Executor.
*/
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 0f7e143..42d7d52 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -16,21 +16,17 @@
package com.android.systemui.wallpapers;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -40,8 +36,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
-import android.view.Display;
-import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -49,8 +43,11 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.wallpapers.canvas.ImageCanvasWallpaperRenderer;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
@@ -59,6 +56,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -78,15 +76,28 @@
private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private final ArraySet<RectF> mColorAreas = new ArraySet<>();
private volatile int mPages = 1;
+ private boolean mPagesComputed = false;
private HandlerThread mWorker;
// scaled down version
private Bitmap mMiniBitmap;
private final FeatureFlags mFeatureFlags;
+ // used in canvasEngine to load/unload the bitmap and extract the colors
+ @Background
+ private final DelayableExecutor mBackgroundExecutor;
+ private static final int DELAY_UNLOAD_BITMAP = 2000;
+
+ @Main
+ private final Executor mMainExecutor;
+
@Inject
- public ImageWallpaper(FeatureFlags featureFlags) {
+ public ImageWallpaper(FeatureFlags featureFlags,
+ @Background DelayableExecutor backgroundExecutor,
+ @Main Executor mainExecutor) {
super();
mFeatureFlags = featureFlags;
+ mBackgroundExecutor = backgroundExecutor;
+ mMainExecutor = mainExecutor;
}
@Override
@@ -339,7 +350,6 @@
imgArea.left = 0;
imgArea.right = 1;
}
-
return imgArea;
}
@@ -510,69 +520,85 @@
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
-
- // time [ms] before unloading the wallpaper after it is loaded
- private static final int DELAY_FORGET_WALLPAPER = 5000;
-
- private final Runnable mUnloadWallpaperCallback = this::unloadWallpaper;
-
private WallpaperManager mWallpaperManager;
- private ImageCanvasWallpaperRenderer mImageCanvasWallpaperRenderer;
+ private final WallpaperColorExtractor mWallpaperColorExtractor;
+ private SurfaceHolder mSurfaceHolder;
+ @VisibleForTesting
+ static final int MIN_SURFACE_WIDTH = 128;
+ @VisibleForTesting
+ static final int MIN_SURFACE_HEIGHT = 128;
private Bitmap mBitmap;
+ private boolean mWideColorGamut = false;
- private Display mDisplay;
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
-
- private AsyncTask<Void, Void, Bitmap> mLoader;
- private boolean mNeedsDrawAfterLoadingWallpaper = false;
+ /*
+ * Counter to unload the bitmap as soon as possible.
+ * Before any bitmap operation, this is incremented.
+ * After an operation completion, this is decremented (synchronously),
+ * and if the count is 0, unload the bitmap
+ */
+ private int mBitmapUsages = 0;
+ private final Object mLock = new Object();
CanvasEngine() {
super();
setFixedSizeAllowed(true);
setShowForAllUsers(true);
- }
+ mWallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ CanvasEngine.this.onColorsProcessed(regions, colors);
+ }
- void trimMemory(int level) {
- if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
- && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
- && isBitmapLoaded()) {
- if (DEBUG) {
- Log.d(TAG, "trimMemory");
- }
- unloadWallpaper();
+ @Override
+ public void onMiniBitmapUpdated() {
+ CanvasEngine.this.onMiniBitmapUpdated();
+ }
+
+ @Override
+ public void onActivated() {
+ setOffsetNotificationsEnabled(true);
+ }
+
+ @Override
+ public void onDeactivated() {
+ setOffsetNotificationsEnabled(false);
+ }
+ });
+
+ // if the number of pages is already computed, transmit it to the color extractor
+ if (mPagesComputed) {
+ mWallpaperColorExtractor.onPageChanged(mPages);
}
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
if (DEBUG) {
Log.d(TAG, "onCreate");
}
+ mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
+ mSurfaceHolder = surfaceHolder;
+ Rect dimensions = mWallpaperManager.peekBitmapDimensions();
+ int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
+ int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
+ mSurfaceHolder.setFixedSize(width, height);
- mWallpaperManager = getSystemService(WallpaperManager.class);
- super.onCreate(surfaceHolder);
-
- final Context displayContext = getDisplayContext();
- final int displayId = displayContext == null ? DEFAULT_DISPLAY :
- displayContext.getDisplayId();
- DisplayManager dm = getSystemService(DisplayManager.class);
- if (dm != null) {
- mDisplay = dm.getDisplay(displayId);
- if (mDisplay == null) {
- Log.e(TAG, "Cannot find display! Fallback to default.");
- mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
- }
- }
- setOffsetNotificationsEnabled(false);
-
- mImageCanvasWallpaperRenderer = new ImageCanvasWallpaperRenderer(surfaceHolder);
- loadWallpaper(false);
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, null);
+ getDisplaySizeAndUpdateColorExtractor();
+ Trace.endSection();
}
@Override
public void onDestroy() {
- super.onDestroy();
- unloadWallpaper();
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(this);
+ mWallpaperColorExtractor.cleanUp();
+ unloadBitmap();
}
@Override
@@ -581,31 +607,30 @@
}
@Override
+ public boolean shouldWaitForEngineShown() {
+ return true;
+ }
+
+ @Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (DEBUG) {
Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
}
- super.onSurfaceChanged(holder, format, width, height);
- mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
- drawFrame(false);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
- super.onSurfaceDestroyed(holder);
if (DEBUG) {
Log.i(TAG, "onSurfaceDestroyed");
}
- mImageCanvasWallpaperRenderer.setSurfaceHolder(null);
+ mSurfaceHolder = null;
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
- super.onSurfaceCreated(holder);
if (DEBUG) {
Log.i(TAG, "onSurfaceCreated");
}
- mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
}
@Override
@@ -613,135 +638,90 @@
if (DEBUG) {
Log.d(TAG, "onSurfaceRedrawNeeded");
}
- super.onSurfaceRedrawNeeded(holder);
- // At the end of this method we should have drawn into the surface.
- // This means that the bitmap should be loaded synchronously if
- // it was already unloaded.
- if (!isBitmapLoaded()) {
- setBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+ drawFrame();
+ }
+
+ private void drawFrame() {
+ mBackgroundExecutor.execute(this::drawFrameSynchronized);
+ }
+
+ private void drawFrameSynchronized() {
+ synchronized (mLock) {
+ drawFrameInternal();
}
- drawFrame(true);
}
- private DisplayInfo getDisplayInfo() {
- mDisplay.getDisplayInfo(mTmpDisplayInfo);
- return mTmpDisplayInfo;
- }
-
- private void drawFrame(boolean forceRedraw) {
- if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
+ private void drawFrameInternal() {
+ if (mSurfaceHolder == null) {
Log.e(TAG, "attempt to draw a frame without a valid surface");
return;
}
+ // load the wallpaper if not already done
if (!isBitmapLoaded()) {
- // ensure that we load the wallpaper.
- // if the wallpaper is currently loading, this call will have no effect.
- loadWallpaper(true);
- return;
- }
- mImageCanvasWallpaperRenderer.drawFrame(mBitmap, forceRedraw);
- }
-
- private void setBitmap(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Attempt to set a null bitmap");
- } else if (mBitmap == bitmap) {
- Log.e(TAG, "The value of bitmap is the same");
- } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
+ loadWallpaperAndDrawFrameInternal();
} else {
- if (mBitmap != null) {
- mBitmap.recycle();
- }
- mBitmap = bitmap;
+ mBitmapUsages++;
+
+ // drawing is done on the main thread
+ mMainExecutor.execute(() -> {
+ drawFrameOnCanvas(mBitmap);
+ reportEngineShown(false);
+ unloadBitmapIfNotUsed();
+ });
}
}
- private boolean isBitmapLoaded() {
+ @VisibleForTesting
+ void drawFrameOnCanvas(Bitmap bitmap) {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
+ Surface surface = mSurfaceHolder.getSurface();
+ Canvas canvas = mWideColorGamut
+ ? surface.lockHardwareWideColorGamutCanvas()
+ : surface.lockHardwareCanvas();
+ if (canvas != null) {
+ Rect dest = mSurfaceHolder.getSurfaceFrame();
+ try {
+ canvas.drawBitmap(bitmap, null, dest, null);
+ } finally {
+ surface.unlockCanvasAndPost(canvas);
+ }
+ }
+ Trace.endSection();
+ }
+
+ @VisibleForTesting
+ boolean isBitmapLoaded() {
return mBitmap != null && !mBitmap.isRecycled();
}
- /**
- * Loads the wallpaper on background thread and schedules updating the surface frame,
- * and if {@code needsDraw} is set also draws a frame.
- *
- * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
- * the active request).
- *
- */
- private void loadWallpaper(boolean needsDraw) {
- mNeedsDrawAfterLoadingWallpaper |= needsDraw;
- if (mLoader != null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping loadWallpaper, already in flight ");
- }
- return;
- }
- mLoader = new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- Throwable exception;
- try {
- Bitmap wallpaper = mWallpaperManager.getBitmap(true /* hardware */);
- if (wallpaper != null
- && wallpaper.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
- throw new RuntimeException("Wallpaper is too large to draw!");
- }
- return wallpaper;
- } catch (RuntimeException | OutOfMemoryError e) {
- exception = e;
- }
-
- if (isCancelled()) {
- return null;
- }
-
- // Note that if we do fail at this, and the default wallpaper can't
- // be loaded, we will go into a cycle. Don't do a build where the
- // default wallpaper can't be loaded.
- Log.w(TAG, "Unable to load wallpaper!", exception);
- try {
- mWallpaperManager.clear();
- } catch (IOException ex) {
- // now we're really screwed.
- Log.w(TAG, "Unable reset to default wallpaper!", ex);
- }
-
- if (isCancelled()) {
- return null;
- }
-
- try {
- return mWallpaperManager.getBitmap(true /* hardware */);
- } catch (RuntimeException | OutOfMemoryError e) {
- Log.w(TAG, "Unable to load default wallpaper!", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- setBitmap(bitmap);
-
- if (mNeedsDrawAfterLoadingWallpaper) {
- drawFrame(true);
- }
-
- mLoader = null;
- mNeedsDrawAfterLoadingWallpaper = false;
- scheduleUnloadWallpaper();
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ private void unloadBitmapIfNotUsed() {
+ mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
}
- private void unloadWallpaper() {
- if (mLoader != null) {
- mLoader.cancel(false);
- mLoader = null;
+ private void unloadBitmapIfNotUsedSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages -= 1;
+ if (mBitmapUsages <= 0) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
+ }
}
+ }
+ private void unloadBitmap() {
+ mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
+ }
+
+ private void unloadBitmapSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
+ }
+ }
+
+ private void unloadBitmapInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
if (mBitmap != null) {
mBitmap.recycle();
}
@@ -750,12 +730,131 @@
final Surface surface = getSurfaceHolder().getSurface();
surface.hwuiDestroy();
mWallpaperManager.forgetLoadedWallpaper();
+ Trace.endSection();
}
- private void scheduleUnloadWallpaper() {
- Handler handler = getMainThreadHandler();
- handler.removeCallbacks(mUnloadWallpaperCallback);
- handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
+ private void loadWallpaperAndDrawFrameInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
+ boolean loadSuccess = false;
+ Bitmap bitmap;
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ if (bitmap != null
+ && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
+ throw new RuntimeException("Wallpaper is too large to draw!");
+ }
+ } catch (RuntimeException | OutOfMemoryError exception) {
+
+ // Note that if we do fail at this, and the default wallpaper can't
+ // be loaded, we will go into a cycle. Don't do a build where the
+ // default wallpaper can't be loaded.
+ Log.w(TAG, "Unable to load wallpaper!", exception);
+ try {
+ mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
+ } catch (IOException ex) {
+ // now we're really screwed.
+ Log.w(TAG, "Unable reset to default wallpaper!", ex);
+ }
+
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ } catch (RuntimeException | OutOfMemoryError e) {
+ Log.w(TAG, "Unable to load default wallpaper!", e);
+ bitmap = null;
+ }
+ }
+
+ if (bitmap == null) {
+ Log.w(TAG, "Could not load bitmap");
+ } else if (bitmap.isRecycled()) {
+ Log.e(TAG, "Attempt to load a recycled bitmap");
+ } else if (mBitmap == bitmap) {
+ Log.e(TAG, "Loaded a bitmap that was already loaded");
+ } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
+ Log.e(TAG, "Attempt to load an invalid wallpaper of length "
+ + bitmap.getWidth() + "x" + bitmap.getHeight());
+ } else {
+ // at this point, loading is done correctly.
+ loadSuccess = true;
+ // recycle the previously loaded bitmap
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ mBitmap = bitmap;
+ mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(
+ WallpaperManager.FLAG_SYSTEM);
+
+ // +2 usages for the color extraction and the delayed unload.
+ mBitmapUsages += 2;
+ recomputeColorExtractorMiniBitmap();
+ drawFrameInternal();
+
+ /*
+ * after loading, the bitmap will be unloaded after all these conditions:
+ * - the frame is redrawn
+ * - the mini bitmap from color extractor is recomputed
+ * - the DELAY_UNLOAD_BITMAP has passed
+ */
+ mBackgroundExecutor.executeDelayed(
+ this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
+ }
+ // even if the bitmap cannot be loaded, call reportEngineShown
+ if (!loadSuccess) reportEngineShown(false);
+ Trace.endSection();
+ }
+
+ private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
+ try {
+ notifyLocalColorsChanged(regions, colors);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ @VisibleForTesting
+ void recomputeColorExtractorMiniBitmap() {
+ mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ }
+
+ @VisibleForTesting
+ void onMiniBitmapUpdated() {
+ unloadBitmapIfNotUsed();
+ }
+
+ @Override
+ public boolean supportsLocalColorExtraction() {
+ return true;
+ }
+
+ @Override
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ // this call will activate the offset notifications
+ // if no colors were being processed before
+ mWallpaperColorExtractor.addLocalColorsAreas(regions);
+ }
+
+ @Override
+ public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+ // this call will deactivate the offset notifications
+ // if we are no longer processing colors
+ mWallpaperColorExtractor.removeLocalColorAreas(regions);
+ }
+
+ @Override
+ public void onOffsetsChanged(float xOffset, float yOffset,
+ float xOffsetStep, float yOffsetStep,
+ int xPixelOffset, int yPixelOffset) {
+ final int pages;
+ if (xOffsetStep > 0 && xOffsetStep <= 1) {
+ pages = Math.round(1 / xOffsetStep) + 1;
+ } else {
+ pages = 1;
+ }
+ if (pages != mPages || !mPagesComputed) {
+ mPages = pages;
+ mPagesComputed = true;
+ mWallpaperColorExtractor.onPageChanged(mPages);
+ }
}
@Override
@@ -764,13 +863,46 @@
}
@Override
- public void onDisplayChanged(int displayId) {
+ public void onDisplayRemoved(int displayId) {
}
@Override
- public void onDisplayRemoved(int displayId) {
+ public void onDisplayChanged(int displayId) {
+ // changes the display in the color extractor
+ // the new display dimensions will be used in the next color computation
+ if (displayId == getDisplayContext().getDisplayId()) {
+ getDisplaySizeAndUpdateColorExtractor();
+ }
+ }
+ private void getDisplaySizeAndUpdateColorExtractor() {
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+ }
+
+
+ @Override
+ protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ super.dump(prefix, fd, out, args);
+ out.print(prefix); out.print("Engine="); out.println(this);
+ out.print(prefix); out.print("valid surface=");
+ out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
+ ? getSurfaceHolder().getSurface().isValid()
+ : "null");
+
+ out.print(prefix); out.print("surface frame=");
+ out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mBitmap == null ? "null"
+ : mBitmap.isRecycled() ? "recycled"
+ : mBitmap.getWidth() + "x" + mBitmap.getHeight());
+
+ mWallpaperColorExtractor.dump(prefix, fd, out, args);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
deleted file mode 100644
index fdba16e..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
+++ /dev/null
@@ -1,145 +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.wallpapers.canvas;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Helper to draw a wallpaper on a surface.
- * It handles the geometry regarding the dimensions of the display and the wallpaper,
- * and rescales the surface and the wallpaper accordingly.
- */
-public class ImageCanvasWallpaperRenderer {
-
- private static final String TAG = ImageCanvasWallpaperRenderer.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private SurfaceHolder mSurfaceHolder;
- //private Bitmap mBitmap = null;
-
- @VisibleForTesting
- static final int MIN_SURFACE_WIDTH = 128;
- @VisibleForTesting
- static final int MIN_SURFACE_HEIGHT = 128;
-
- private boolean mSurfaceRedrawNeeded;
-
- private int mLastSurfaceWidth = -1;
- private int mLastSurfaceHeight = -1;
-
- public ImageCanvasWallpaperRenderer(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Set the surface holder on which to draw.
- * Should be called when the surface holder is created or changed
- * @param surfaceHolder the surface on which to draw the wallpaper
- */
- public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Check if a surface holder is loaded
- * @return true if a valid surfaceHolder has been set.
- */
- public boolean isSurfaceHolderLoaded() {
- return mSurfaceHolder != null;
- }
-
- /**
- * Computes and set the surface dimensions, by using the play and the bitmap dimensions.
- * The Bitmap must be loaded before any call to this function
- */
- private boolean updateSurfaceSize(Bitmap bitmap) {
- int surfaceWidth = Math.max(MIN_SURFACE_WIDTH, bitmap.getWidth());
- int surfaceHeight = Math.max(MIN_SURFACE_HEIGHT, bitmap.getHeight());
- boolean surfaceChanged =
- surfaceWidth != mLastSurfaceWidth || surfaceHeight != mLastSurfaceHeight;
- if (surfaceChanged) {
- /*
- Used a fixed size surface, because we are special. We can do
- this because we know the current design of window animations doesn't
- cause this to break.
- */
- mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- }
- return surfaceChanged;
- }
-
- /**
- * Draw a the wallpaper on the surface.
- * The bitmap and the surface must be loaded before calling
- * this function.
- * @param forceRedraw redraw the wallpaper even if no changes are detected
- */
- public void drawFrame(Bitmap bitmap, boolean forceRedraw) {
-
- if (bitmap == null || bitmap.isRecycled()) {
- Log.e(TAG, "Attempt to draw frame before background is loaded:");
- return;
- }
-
- if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
- return;
- }
-
- mSurfaceRedrawNeeded |= forceRedraw;
- boolean surfaceChanged = updateSurfaceSize(bitmap);
-
- boolean redrawNeeded = surfaceChanged || mSurfaceRedrawNeeded;
- mSurfaceRedrawNeeded = false;
-
- if (!redrawNeeded) {
- if (DEBUG) {
- Log.d(TAG, "Suppressed drawFrame since redraw is not needed ");
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Redrawing wallpaper");
- }
- drawWallpaperWithCanvas(bitmap);
- }
-
- @VisibleForTesting
- void drawWallpaperWithCanvas(Bitmap bitmap) {
- Canvas c = mSurfaceHolder.lockHardwareCanvas();
- if (c != null) {
- Rect dest = mSurfaceHolder.getSurfaceFrame();
- Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
- + mLastSurfaceWidth + "x" + mLastSurfaceHeight);
- try {
- c.drawBitmap(bitmap, null, dest, null);
- } finally {
- mSurfaceHolder.unlockCanvasAndPost(c);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
new file mode 100644
index 0000000..e2e4555
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
@@ -0,0 +1,400 @@
+/*
+ * 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.wallpapers.canvas;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.Assert;
+import com.android.systemui.wallpapers.ImageWallpaper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used by the {@link ImageWallpaper} to extract colors from areas of a wallpaper.
+ * It uses a background executor, and uses callbacks to inform that the work is done.
+ * It uses a downscaled version of the wallpaper to extract the colors.
+ */
+public class WallpaperColorExtractor {
+
+ private Bitmap mMiniBitmap;
+
+ @VisibleForTesting
+ static final int SMALL_SIDE = 128;
+
+ private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
+
+ private int mDisplayWidth = -1;
+ private int mDisplayHeight = -1;
+ private int mPages = -1;
+ private int mBitmapWidth = -1;
+ private int mBitmapHeight = -1;
+
+ private final Object mLock = new Object();
+
+ private final List<RectF> mPendingRegions = new ArrayList<>();
+ private final Set<RectF> mProcessedRegions = new ArraySet<>();
+
+ @Background
+ private final Executor mBackgroundExecutor;
+
+ private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+
+ /**
+ * Interface to handle the callbacks after the different steps of the color extraction
+ */
+ public interface WallpaperColorExtractorCallback {
+ /**
+ * Callback after the colors of new regions have been extracted
+ * @param regions the list of new regions that have been processed
+ * @param colors the resulting colors for these regions, in the same order as the regions
+ */
+ void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors);
+
+ /**
+ * Callback after the mini bitmap is computed, to indicate that the wallpaper bitmap is
+ * no longer used by the color extractor and can be safely recycled
+ */
+ void onMiniBitmapUpdated();
+
+ /**
+ * Callback to inform that the extractor has started processing colors
+ */
+ void onActivated();
+
+ /**
+ * Callback to inform that no more colors are being processed
+ */
+ void onDeactivated();
+ }
+
+ /**
+ * Creates a new color extractor.
+ * @param backgroundExecutor the executor on which the color extraction will be performed
+ * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * the color extractor.
+ */
+ public WallpaperColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ mBackgroundExecutor = backgroundExecutor;
+ mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ }
+
+ /**
+ * Used by the outside to inform that the display size has changed.
+ * The new display size will be used in the next computations, but the current colors are
+ * not recomputed.
+ */
+ public void setDisplayDimensions(int displayWidth, int displayHeight) {
+ mBackgroundExecutor.execute(() ->
+ setDisplayDimensionsSynchronized(displayWidth, displayHeight));
+ }
+
+ private void setDisplayDimensionsSynchronized(int displayWidth, int displayHeight) {
+ synchronized (mLock) {
+ if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * @return whether color extraction is currently in use
+ */
+ private boolean isActive() {
+ return mPendingRegions.size() + mProcessedRegions.size() > 0;
+ }
+
+ /**
+ * Should be called when the wallpaper is changed.
+ * This will recompute the mini bitmap
+ * and restart the extraction of all areas
+ * @param bitmap the new wallpaper
+ */
+ public void onBitmapChanged(@NonNull Bitmap bitmap) {
+ mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+ }
+
+ private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
+ synchronized (mLock) {
+ if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+ Log.e(TAG, "Attempt to extract colors from an invalid bitmap");
+ return;
+ }
+ mBitmapWidth = bitmap.getWidth();
+ mBitmapHeight = bitmap.getHeight();
+ mMiniBitmap = createMiniBitmap(bitmap);
+ mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ recomputeColors();
+ }
+ }
+
+ /**
+ * Should be called when the number of pages is changed
+ * This will restart the extraction of all areas
+ * @param pages the total number of pages of the launcher
+ */
+ public void onPageChanged(int pages) {
+ mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+ }
+
+ private void onPageChangedSynchronized(int pages) {
+ synchronized (mLock) {
+ if (mPages == pages) return;
+ mPages = pages;
+ if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
+ recomputeColors();
+ }
+ }
+ }
+
+ // helper to recompute colors, to be called in synchronized methods
+ private void recomputeColors() {
+ mPendingRegions.addAll(mProcessedRegions);
+ mProcessedRegions.clear();
+ processColorsInternal();
+ }
+
+ /**
+ * Add new regions to extract
+ * This will trigger the color extraction and call the callback only for these new regions
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ if (regions.size() > 0) {
+ mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+ } else {
+ Log.w(TAG, "Attempt to add colors with an empty list");
+ }
+ }
+
+ private void addLocalColorsAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.addAll(regions);
+ if (!wasActive && isActive()) {
+ mWallpaperColorExtractorCallback.onActivated();
+ }
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * Remove regions to extract. If a color extraction is ongoing does not stop it.
+ * But if there are subsequent changes that restart the extraction, the removed regions
+ * will not be recomputed.
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void removeLocalColorAreas(@NonNull List<RectF> regions) {
+ mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+ }
+
+ private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.removeAll(regions);
+ regions.forEach(mProcessedRegions::remove);
+ if (wasActive && !isActive()) {
+ mWallpaperColorExtractorCallback.onDeactivated();
+ }
+ }
+ }
+
+ /**
+ * Clean up the memory (in particular, the mini bitmap) used by this class.
+ */
+ public void cleanUp() {
+ mBackgroundExecutor.execute(this::cleanUpSynchronized);
+ }
+
+ private void cleanUpSynchronized() {
+ synchronized (mLock) {
+ if (mMiniBitmap != null) {
+ mMiniBitmap.recycle();
+ mMiniBitmap = null;
+ }
+ mProcessedRegions.clear();
+ mPendingRegions.clear();
+ }
+ }
+
+ private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
+ Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ // if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
+ int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
+ float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
+ Bitmap result = createMiniBitmap(bitmap,
+ (int) (scale * bitmap.getWidth()),
+ (int) (scale * bitmap.getHeight()));
+ Trace.endSection();
+ return result;
+ }
+
+ @VisibleForTesting
+ Bitmap createMiniBitmap(@NonNull Bitmap bitmap, int width, int height) {
+ return Bitmap.createScaledBitmap(bitmap, width, height, false);
+ }
+
+ private WallpaperColors getLocalWallpaperColors(@NonNull RectF area) {
+ RectF imageArea = pageToImgRect(area);
+ if (imageArea == null || !LOCAL_COLOR_BOUNDS.contains(imageArea)) {
+ return null;
+ }
+ Rect subImage = new Rect(
+ (int) Math.floor(imageArea.left * mMiniBitmap.getWidth()),
+ (int) Math.floor(imageArea.top * mMiniBitmap.getHeight()),
+ (int) Math.ceil(imageArea.right * mMiniBitmap.getWidth()),
+ (int) Math.ceil(imageArea.bottom * mMiniBitmap.getHeight()));
+ if (subImage.isEmpty()) {
+ // Do not notify client. treat it as too small to sample
+ return null;
+ }
+ return getLocalWallpaperColors(subImage);
+ }
+
+ @VisibleForTesting
+ WallpaperColors getLocalWallpaperColors(@NonNull Rect subImage) {
+ Assert.isNotMainThread();
+ Bitmap colorImg = Bitmap.createBitmap(mMiniBitmap,
+ subImage.left, subImage.top, subImage.width(), subImage.height());
+ return WallpaperColors.fromBitmap(colorImg);
+ }
+
+ /**
+ * Transform the logical coordinates into wallpaper coordinates.
+ *
+ * Logical coordinates are organised such that the various pages are non-overlapping. So,
+ * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
+ *
+ * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
+ * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
+ * pages increase.
+ * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
+ * last page is at position (1-Wr) and the others are regularly spread on the range [0-
+ * (1-Wr)].
+ */
+ private RectF pageToImgRect(RectF area) {
+ // Width of a page for the caller of this API.
+ float virtualPageWidth = 1f / (float) mPages;
+ float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
+ float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
+ int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
+
+ if (mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ Log.e(TAG, "Trying to extract colors with invalid display dimensions");
+ return null;
+ }
+
+ RectF imgArea = new RectF();
+ imgArea.bottom = area.bottom;
+ imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mBitmapHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mBitmapWidth > 0 ? mappedScreenWidth / (float) mBitmapWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
+ imgArea.left = MathUtils.constrain(
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ imgArea.right = MathUtils.constrain(
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ if (imgArea.left > imgArea.right) {
+ // take full page
+ imgArea.left = 0;
+ imgArea.right = 1;
+ }
+ return imgArea;
+ }
+
+ /**
+ * Extract the colors from the pending regions,
+ * then notify the callback with the resulting colors for these regions
+ * This method should only be called synchronously
+ */
+ private void processColorsInternal() {
+ /*
+ * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
+ * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
+ * will be called again when the bitmap is loaded and the miniBitmap is computed.
+ */
+ if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
+
+ /*
+ * if the screen size or number of pages is not yet known, exit
+ * the function will be called again once the screen size and page are known
+ */
+ if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
+
+ Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ List<WallpaperColors> processedColors = new ArrayList<>();
+ for (int i = 0; i < mPendingRegions.size(); i++) {
+ RectF nextArea = mPendingRegions.get(i);
+ WallpaperColors colors = getLocalWallpaperColors(nextArea);
+
+ mProcessedRegions.add(nextArea);
+ processedColors.add(colors);
+ }
+ List<RectF> processedRegions = new ArrayList<>(mPendingRegions);
+ mPendingRegions.clear();
+ Trace.endSection();
+
+ mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ }
+
+ /**
+ * Called to dump current state.
+ * @param prefix prefix.
+ * @param fd fd.
+ * @param out out.
+ * @param args args.
+ */
+ public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ out.print(prefix); out.print("display="); out.println(mDisplayWidth + "x" + mDisplayHeight);
+ out.print(prefix); out.print("mPages="); out.println(mPages);
+
+ out.print(prefix); out.print("bitmap dimensions=");
+ out.println(mBitmapWidth + "x" + mBitmapHeight);
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mMiniBitmap == null ? "null"
+ : mMiniBitmap.isRecycled() ? "recycled"
+ : mMiniBitmap.getWidth() + "x" + mMiniBitmap.getHeight());
+
+ out.print(prefix); out.print("PendingRegions size="); out.print(mPendingRegions.size());
+ out.print(prefix); out.print("ProcessedRegions size="); out.print(mProcessedRegions.size());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 46226ef..63729e3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -607,7 +607,7 @@
public void testTriesToAuthenticate_whenKeyguard() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -617,7 +617,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -630,7 +630,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -654,7 +654,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
// Stop scanning when bouncer becomes visible
@@ -668,7 +668,7 @@
@Test
public void testTriesToAuthenticate_whenAssistant() {
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAssistantVisible(true);
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -683,7 +683,7 @@
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -693,7 +693,7 @@
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -705,7 +705,7 @@
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -717,7 +717,7 @@
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -738,7 +738,7 @@
public void testFaceAndFingerprintLockout_onlyFace() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
faceAuthLockedOut();
@@ -749,7 +749,7 @@
public void testFaceAndFingerprintLockout_onlyFingerprint() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
@@ -761,7 +761,7 @@
public void testFaceAndFingerprintLockout() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
faceAuthLockedOut();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
@@ -860,7 +860,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1033,8 +1033,7 @@
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// THEN we shouldn't listen for fingerprints
@@ -1049,8 +1048,7 @@
public void testOccludingAppRequestsFingerprint() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
// WHEN an occluding app requests fp
mKeyguardUpdateMonitor.requestFingerprintAuthOnOccludingApp(true);
@@ -1142,7 +1140,7 @@
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
when(mKeyguardBypassController.canBypass()).thenReturn(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
// WHEN status bar state reports a change to the keyguard that would normally indicate to
// start running face auth
@@ -1153,8 +1151,9 @@
// listening state to update
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false);
- // WHEN biometric listening state is updated
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ // WHEN biometric listening state is updated when showing state changes from false => true
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
// THEN face unlock is running
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(true);
@@ -1475,6 +1474,27 @@
}
@Test
+ public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+ throws RemoteException {
+ // Preconditions for face auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ deviceNotGoingToSleep();
+ mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ deviceGoingToSleep();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
@@ -1499,7 +1519,7 @@
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1508,7 +1528,7 @@
mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
// Make sure keyguard is going away after face auth attempt, and that it calls
// updateBiometricStateListeningState.
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
mTestableLooper.processAllMessages();
verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived,
@@ -1568,7 +1588,7 @@
}
private void keyguardIsVisible() {
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
}
private void triggerAuthInterrupt() {
@@ -1661,6 +1681,10 @@
mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
}
+ private void deviceGoingToSleep() {
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1);
+ }
+
private void deviceIsInteractive() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 8fc0489..986e7cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -718,7 +718,7 @@
}
@Test
- fun animatesViewRemovalFromStartToEnd() {
+ fun animatesViewRemovalFromStartToEnd_viewHasSiblings() {
setUpRootWithChildren()
val child = rootView.getChildAt(0)
@@ -742,6 +742,35 @@
}
@Test
+ fun animatesViewRemovalFromStartToEnd_viewHasNoSiblings() {
+ rootView = LinearLayout(mContext)
+ (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
+ (rootView as LinearLayout).weightSum = 1f
+
+ val onlyChild = View(mContext)
+ rootView.addView(onlyChild)
+ forceLayout()
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ onlyChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ interpolator = Interpolators.LINEAR
+ )
+
+ assertTrue(success)
+ assertNotNull(onlyChild.getTag(R.id.tag_animator))
+ checkBounds(onlyChild, l = 0, t = 0, r = 200, b = 100)
+ advanceAnimation(onlyChild, 0.5f)
+ checkBounds(onlyChild, l = 0, t = 0, r = 100, b = 100)
+ advanceAnimation(onlyChild, 1.0f)
+ checkBounds(onlyChild, l = 0, t = 0, r = 0, b = 100)
+ endAnimation(rootView)
+ endAnimation(onlyChild)
+ assertEquals(0, rootView.childCount)
+ assertFalse(onlyChild in rootView.children)
+ }
+
+ @Test
fun animatesViewRemovalRespectingDestination() {
// CENTER
setUpRootWithChildren()
@@ -964,6 +993,60 @@
}
@Test
+ fun animateRemoval_runnableRunsWhenAnimationEnds() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ endAnimation(removedView)
+
+ assertEquals(true, runnableRun)
+ }
+
+ @Test
+ fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ cancelAnimation(removedView)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
+ fun animationRemoval_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ advanceAnimation(removedView, 0.5f)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
fun cleansUpListenersCorrectly() {
val firstChild = View(mContext)
firstChild.layoutParams = LinearLayout.LayoutParams(50 /* width */, 100 /* height */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 4a5b23c..d52612b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -322,6 +322,13 @@
}
@Test
+ fun testLayoutParams_hasShowWhenLockedFlag() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
+ .isTrue()
+ }
+
+ @Test
fun testLayoutParams_hasDimbehindWindowFlag() {
val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
val lpFlags = layoutParams.flags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 37bb0c2..0b528a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -117,13 +117,12 @@
}
@Test
- fun testFingerprintTrigger_KeyguardVisible_Ripple() {
- // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
+ fun testFingerprintTrigger_KeyguardShowing_Ripple() {
+ // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
// WHEN fingerprint authenticated
@@ -140,39 +139,15 @@
}
@Test
- fun testFingerprintTrigger_Dreaming_Ripple() {
- // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
- val fpsLocation = Point(5, 5)
- `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
- controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
-
- // WHEN fingerprint authenticated
- val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onBiometricAuthenticated(
- 0 /* userId */,
- BiometricSourceType.FINGERPRINT /* type */,
- false /* isStrongBiometric */)
-
- // THEN update sensor location and show ripple
- verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
- verify(rippleView).startUnlockedRipple(any())
- }
-
- @Test
- fun testFingerprintTrigger_KeyguardNotVisible_NotDreaming_NoRipple() {
+ fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
// GIVEN fp exists & user doesn't need strong auth
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
- // WHEN keyguard is NOT visible & fingerprint authenticated
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ // WHEN keyguard is NOT showing & fingerprint authenticated
+ `when`(keyguardStateController.isShowing).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
@@ -186,11 +161,11 @@
@Test
fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
- // GIVEN fp exists & keyguard is visible
+ // GIVEN fp exists & keyguard is showing
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
// WHEN user needs strong auth & fingerprint authenticated
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
@@ -207,12 +182,12 @@
@Test
fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is visible & strong auth isn't required
+ // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
// WHEN bypass is enabled & face authenticated
@@ -299,7 +274,7 @@
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -317,7 +292,7 @@
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 434cb48..25bc91f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -96,7 +96,7 @@
@Mock
private lateinit var removalPendingStore: PendingRemovalStore
- private lateinit var executor: Executor
+ private lateinit var mainExecutor: Executor
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
@@ -108,11 +108,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
- executor = FakeExecutor(FakeSystemClock())
- `when`(mockContext.mainExecutor).thenReturn(executor)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ `when`(mockContext.mainExecutor).thenReturn(mainExecutor)
broadcastDispatcher = TestBroadcastDispatcher(
mockContext,
+ mainExecutor,
testableLooper.looper,
mock(Executor::class.java),
mock(DumpManager::class.java),
@@ -148,9 +149,9 @@
@Test
fun testAddingReceiverToCorrectUBR_executor() {
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mainExecutor, user0)
broadcastDispatcher.registerReceiver(
- broadcastReceiverOther, intentFilterOther, executor, user1)
+ broadcastReceiverOther, intentFilterOther, mainExecutor, user1)
testableLooper.processAllMessages()
@@ -427,8 +428,9 @@
private class TestBroadcastDispatcher(
context: Context,
- bgLooper: Looper,
- executor: Executor,
+ mainExecutor: Executor,
+ backgroundRunningLooper: Looper,
+ backgroundRunningExecutor: Executor,
dumpManager: DumpManager,
logger: BroadcastDispatcherLogger,
userTracker: UserTracker,
@@ -436,8 +438,9 @@
var mockUBRMap: Map<Int, UserBroadcastDispatcher>
) : BroadcastDispatcher(
context,
- bgLooper,
- executor,
+ mainExecutor,
+ backgroundRunningLooper,
+ backgroundRunningExecutor,
dumpManager,
logger,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6436981..781dc15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -144,6 +144,12 @@
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine);
+ ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doAnswer(invocation ->
+ when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+ ).when(mHost).setPulsePending(boolCaptor.capture());
+
+ when(mHost.isPulsingBlocked()).thenReturn(false);
mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
@@ -160,6 +166,29 @@
}
@Test
+ public void testOnNotification_noPulseIfPulseIsNotPendingAnymore() {
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+ doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
+
+ mTriggers.transitionTo(UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+ clearInvocations(mMachine);
+ when(mHost.isPulsingBlocked()).thenReturn(false);
+
+ // GIVEN pulsePending = false
+ when(mHost.isPulsePending()).thenReturn(false);
+
+ // WHEN prox check returns FAR
+ mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
+ mProximitySensor.alertListeners();
+
+ // THEN don't request pulse because the pending pulse was abandoned early
+ verify(mMachine, never()).requestPulse(anyInt());
+ }
+
+ @Test
public void testTransitionTo_disablesAndEnablesTouchSensors() {
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
@@ -237,6 +266,11 @@
when(mSessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD))
.thenReturn(keyguardSessionId);
+ ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doAnswer(invocation ->
+ when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+ ).when(mHost).setPulsePending(boolCaptor.capture());
+
// WHEN quick pick up is triggered
mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 2448f1a..849ac5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -297,10 +297,10 @@
}
/**
- * Ensures margin is applied
+ * Ensures default margin is applied
*/
@Test
- public void testMargin() {
+ public void testDefaultMargin() {
final int margin = 5;
final ComplicationLayoutEngine engine =
new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0);
@@ -373,6 +373,74 @@
}
/**
+ * Ensures complication margin is applied
+ */
+ @Test
+ public void testComplicationMargin() {
+ final int defaultMargin = 5;
+ final int complicationMargin = 10;
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ complicationMargin),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, firstViewInfo);
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, secondViewInfo);
+
+ firstViewInfo.clearInvocations();
+ secondViewInfo.clearInvocations();
+
+ final ViewInfo thirdViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 1),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, thirdViewInfo);
+
+ // The first added view should now be underneath the second view.
+ verifyChange(firstViewInfo, false, lp -> {
+ assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.topMargin).isEqualTo(complicationMargin);
+ });
+
+ // The second view should be in underneath the third view.
+ verifyChange(secondViewInfo, false, lp -> {
+ assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.getMarginEnd()).isEqualTo(defaultMargin);
+ });
+ }
+
+ /**
* Ensures layout in a particular position updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index 967b30d..cb7e47b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -97,6 +97,35 @@
}
/**
+ * Ensures unspecified margin uses default.
+ */
+ @Test
+ public void testUnspecifiedMarginUsesDefault() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3);
+ assertThat(params.getMargin(10) == 10).isTrue();
+ }
+
+ /**
+ * Ensures specified margin is used instead of default.
+ */
+ @Test
+ public void testSpecifiedMargin() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ assertThat(params.getMargin(5) == 10).isTrue();
+ }
+
+ /**
* Ensures ComplicationLayoutParams is properly duplicated on copy construction.
*/
@Test
@@ -106,12 +135,36 @@
100,
ComplicationLayoutParams.POSITION_TOP,
ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+ assertThat(copy.getDirection() == params.getDirection()).isTrue();
+ assertThat(copy.getPosition() == params.getPosition()).isTrue();
+ assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+ assertThat(copy.height == params.height).isTrue();
+ assertThat(copy.width == params.width).isTrue();
+ }
+
+ /**
+ * Ensures ComplicationLayoutParams is properly duplicated on copy construction with unspecified
+ * margin.
+ */
+ @Test
+ public void testCopyConstructionWithUnspecifiedMargin() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
3);
final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
assertThat(copy.getDirection() == params.getDirection()).isTrue();
assertThat(copy.getPosition() == params.getPosition()).isTrue();
assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.getMargin(1) == params.getMargin(1)).isTrue();
assertThat(copy.height == params.height).isTrue();
assertThat(copy.width == params.width).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 04ff7ae..db6082d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -29,9 +29,12 @@
import android.content.Context;
import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.ImageView;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
@@ -40,6 +43,7 @@
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
import org.junit.Before;
import org.junit.Test;
@@ -79,6 +83,15 @@
@Captor
private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
+ @Mock
+ private ImageView mView;
+
+ @Mock
+ private ActivityStarter mActivityStarter;
+
+ @Mock
+ UiEventLogger mUiEventLogger;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -151,6 +164,30 @@
verify(mDreamOverlayStateController).addComplication(mComplication);
}
+ /**
+ * Ensures clicking home controls chip logs UiEvent.
+ */
+ @Test
+ public void testClick_logsUiEvent() {
+ final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
+ new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
+ mView,
+ mActivityStarter,
+ mContext,
+ mControlsComponent,
+ mUiEventLogger);
+ viewController.onViewAttached();
+
+ final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+ clickListenerCaptor.getValue().onClick(mView);
+ verify(mUiEventLogger).log(
+ DreamHomeControlsComplication.DreamHomeControlsChipViewController
+ .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+ }
+
private void setHaveFavorites(boolean value) {
final List<StructureInfo> favorites = mock(List.class);
when(favorites.isEmpty()).thenReturn(!value);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index d418836..7f55d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -49,6 +49,7 @@
import org.junit.Rule;
import org.junit.Test;
+import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@@ -148,8 +149,12 @@
return false;
}
- private static void executeShellCommand(String cmd) {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd);
+ private void executeShellCommand(String cmd) {
+ try {
+ runShellCommand(cmd);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index eea2e95..7a15680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -33,7 +34,6 @@
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -153,6 +153,21 @@
}
@Test
+ fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
+ var latest: Boolean? = null
+
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ job.cancel()
+
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ job.cancel()
+ }
+
+ @Test
fun dozeAmount() = runBlockingTest {
val values = mutableListOf<Float>()
val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index d4fba41..329c4db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -138,7 +138,7 @@
assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
assertThat(visibleState.icon).isNotNull()
- assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+ assertThat(visibleState.icon.contentDescription).isNotNull()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 5a3a78e..0a4478f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -21,9 +21,11 @@
import android.service.quickaccesswallet.GetWalletCardsResponse
import android.service.quickaccesswallet.QuickAccessWalletClient
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -69,8 +71,16 @@
val job = underTest.state.onEach { latest = it }.launchIn(this)
val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
- assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
- assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+ assertThat(visibleModel.icon)
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = ICON,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ )
+ )
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c5e828e..b6d7559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -21,7 +21,8 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -34,6 +35,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
@@ -46,7 +48,6 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -55,7 +56,15 @@
companion object {
private val INTENT = Intent("some.intent.action")
- private val DRAWABLE = mock<ContainedDrawable>()
+ private val DRAWABLE =
+ mock<Icon> {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
@Parameters(
@@ -236,7 +245,6 @@
state =
KeyguardQuickAffordanceConfig.State.Visible(
icon = DRAWABLE,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
homeControls.onClickedResult =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 19d8412..1dd919a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,7 +19,8 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -32,6 +33,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -101,7 +103,6 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -120,8 +121,8 @@
val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
assertThat(visibleModel.configKey).isEqualTo(configKey)
assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.contentDescriptionResourceId)
- .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
job.cancel()
}
@@ -131,7 +132,6 @@
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -150,8 +150,8 @@
val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
assertThat(visibleModel.configKey).isEqualTo(configKey)
assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.contentDescriptionResourceId)
- .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
job.cancel()
}
@@ -161,7 +161,6 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -182,7 +181,6 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -197,7 +195,14 @@
}
companion object {
- private val ICON: ContainedDrawable = mock()
+ private val ICON: Icon = mock {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index c612091..96544e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -505,7 +505,6 @@
}
KeyguardQuickAffordanceConfig.State.Visible(
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
@@ -543,7 +542,7 @@
private data class TestConfig(
val isVisible: Boolean,
val isClickable: Boolean = false,
- val icon: ContainedDrawable? = null,
+ val icon: Icon? = null,
val canShowWhileLocked: Boolean = false,
val intent: Intent? = null,
) {
@@ -555,6 +554,5 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
- private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 82aa612..5973340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -26,13 +26,13 @@
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.concurrency.FakeRepeatableExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -47,8 +47,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -70,6 +70,8 @@
}
@Mock private lateinit var mockController: MediaController
@Mock private lateinit var mockTransport: MediaController.TransportControls
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var mockBar: SeekBar
private val token1 = MediaSession.Token(1, null)
private val token2 = MediaSession.Token(2, null)
@@ -78,9 +80,10 @@
@Before
fun setUp() {
fakeExecutor = FakeExecutor(FakeSystemClock())
- viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
+ viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
viewModel.logSeek = { }
whenever(mockController.sessionToken).thenReturn(token1)
+ whenever(mockBar.context).thenReturn(context)
// LiveData to run synchronously
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
@@ -454,6 +457,25 @@
}
@Test
+ fun onFalseTapOrTouch() {
+ whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+ whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
+ whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ viewModel.updateController(mockController)
+ val pos = 169
+
+ viewModel.attachTouchHandlers(mockBar)
+ with(viewModel.seekBarListener) {
+ onStartTrackingTouch(mockBar)
+ onProgressChanged(mockBar, pos, true)
+ onStopTrackingTouch(mockBar)
+ }
+
+ // THEN transport controls should not be used
+ verify(mockTransport, never()).seekTo(pos.toLong())
+ }
+
+ @Test
fun queuePollTaskWhenPlaying() {
// GIVEN that the track is playing
val state = PlaybackState.Builder().run {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 37f6434..7c83cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -19,9 +19,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
@@ -90,48 +88,6 @@
assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
}
-
- @Test
- fun setIcon_viewHasIconAndContentDescription() {
- val view = CachingIconView(context)
- val icon = context.getDrawable(R.drawable.ic_celebration)!!
- val contentDescription = "Happy birthday!"
-
- MediaTttUtils.setIcon(view, icon, contentDescription)
-
- assertThat(view.drawable).isEqualTo(icon)
- assertThat(view.contentDescription).isEqualTo(contentDescription)
- }
-
- @Test
- fun setIcon_iconSizeNull_viewSizeDoesNotChange() {
- val view = CachingIconView(context)
- val size = 456
- view.layoutParams = FrameLayout.LayoutParams(size, size)
-
- MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc")
-
- assertThat(view.layoutParams.width).isEqualTo(size)
- assertThat(view.layoutParams.height).isEqualTo(size)
- }
-
- @Test
- fun setIcon_iconSizeProvided_viewSizeUpdates() {
- val view = CachingIconView(context)
- val size = 456
- view.layoutParams = FrameLayout.LayoutParams(size, size)
-
- val newSize = 40
- MediaTttUtils.setIcon(
- view,
- context.getDrawable(R.drawable.ic_cake)!!,
- "desc",
- iconSize = newSize
- )
-
- assertThat(view.layoutParams.width).isEqualTo(newSize)
- assertThat(view.layoutParams.height).isEqualTo(newSize)
- }
}
private const val PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index d41ad48..775dc11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -212,35 +212,27 @@
}
@Test
- fun updateView_isAppIcon_usesAppIconSize() {
+ fun updateView_isAppIcon_usesAppIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME))
+
val chipView = getChipView()
-
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- val expectedSize =
- context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
- assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
- assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
+ assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingRight).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingTop).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(0)
}
@Test
- fun updateView_notAppIcon_usesGenericIconSize() {
+ fun updateView_notAppIcon_usesGenericIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = null))
+
val chipView = getChipView()
-
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- val expectedSize =
- context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver)
- assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
- assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
+ val expectedPadding =
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
+ assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingRight).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingTop).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(expectedPadding)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 098086a..eca3bed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
+import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
@@ -37,9 +38,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -61,7 +64,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class MediaTttChipControllerSenderTest : SysuiTestCase() {
- private lateinit var controllerSender: MediaTttChipControllerSender
+ private lateinit var controllerSender: TestMediaTttChipControllerSender
@Mock
private lateinit var packageManager: PackageManager
@@ -116,7 +119,7 @@
whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
- controllerSender = MediaTttChipControllerSender(
+ controllerSender = TestMediaTttChipControllerSender(
commandQueue,
context,
logger,
@@ -821,6 +824,37 @@
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceFailed() =
ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+
+ private class TestMediaTttChipControllerSender(
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: Lazy<FalsingManager>,
+ falsingCollector: Lazy<FalsingCollector>,
+ ) : MediaTttChipControllerSender(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ uiEventLogger,
+ falsingManager,
+ falsingCollector,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+ }
}
private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 00b1f32..19d2d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -25,6 +25,7 @@
private val controller = MediaProjectionAppSelectorController(
taskListProvider,
+ view,
scope,
appSelectorComponentName
)
@@ -33,7 +34,7 @@
fun initNoRecentTasks_bindsEmptyList() {
taskListProvider.tasks = emptyList()
- controller.init(view)
+ controller.init()
verify(view).bind(emptyList())
}
@@ -44,7 +45,7 @@
createRecentTask(taskId = 1)
)
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
@@ -62,7 +63,7 @@
)
taskListProvider.tasks = tasks
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
@@ -84,7 +85,7 @@
)
taskListProvider.tasks = tasks
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
new file mode 100644
index 0000000..464acb6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Rect
+import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.min
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TaskPreviewSizeProviderTest : SysuiTestCase() {
+
+ private val mockContext: Context = mock()
+ private val resources: Resources = mock()
+ private val windowManager: WindowManager = mock()
+ private val sizeUpdates = arrayListOf<Rect>()
+ private val testConfigurationController = FakeConfigurationController()
+
+ @Before
+ fun setup() {
+ whenever(mockContext.getSystemService(eq(WindowManager::class.java)))
+ .thenReturn(windowManager)
+ whenever(mockContext.resources).thenReturn(resources)
+ }
+
+ @Test
+ fun size_phoneDisplay_thumbnailSizeIsSmallerAndProportionalToTheScreenSize() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+
+ val size = createSizeProvider().size
+
+ assertThat(size).isEqualTo(Rect(0, 0, 100, 150))
+ }
+
+ @Test
+ fun size_tabletDisplay_thumbnailSizeProportionalToTheScreenSizeExcludingTaskbar() {
+ givenDisplay(width = 400, height = 600, isTablet = true)
+ givenTaskbarSize(20)
+
+ val size = createSizeProvider().size
+
+ assertThat(size).isEqualTo(Rect(0, 0, 97, 140))
+ }
+
+ @Test
+ fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+ createSizeProvider()
+
+ givenDisplay(width = 600, height = 400, isTablet = false)
+ testConfigurationController.onConfigurationChanged(Configuration())
+
+ assertThat(sizeUpdates).containsExactly(Rect(0, 0, 150, 100))
+ }
+
+ @Test
+ fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+ val sizeProvider = createSizeProvider()
+
+ givenDisplay(width = 600, height = 400, isTablet = false)
+ testConfigurationController.onConfigurationChanged(Configuration())
+
+ assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
+ }
+
+ private fun givenTaskbarSize(size: Int) {
+ whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size)
+ }
+
+ private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
+ val bounds = Rect(0, 0, width, height)
+ val windowMetrics = WindowMetrics(bounds, null)
+ whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
+ whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+
+ val minDimension = min(width, height)
+
+ // Calculate DPI so the smallest width is either considered as tablet or as phone
+ val targetSmallestWidthDpi =
+ if (isTablet) SMALLEST_WIDTH_DPI_TABLET else SMALLEST_WIDTH_DPI_PHONE
+ val densityDpi = minDimension * DENSITY_DEFAULT / targetSmallestWidthDpi
+
+ val configuration = Configuration(context.resources.configuration)
+ configuration.densityDpi = densityDpi
+ whenever(resources.configuration).thenReturn(configuration)
+ }
+
+ private fun createSizeProvider(): TaskPreviewSizeProvider {
+ val listener =
+ object : TaskPreviewSizeListener {
+ override fun onTaskSizeChanged(size: Rect) {
+ sizeUpdates.add(size)
+ }
+ }
+
+ return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController)
+ .also { it.addCallback(listener) }
+ }
+
+ private companion object {
+ private const val SMALLEST_WIDTH_DPI_TABLET = 800
+ private const val SMALLEST_WIDTH_DPI_PHONE = 400
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 3cad2a0..b847ad0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -277,7 +277,7 @@
// Then the layout changes
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
- verify(mHorizontalLayoutListener).run(); // not invoked
+ verify(mHorizontalLayoutListener).run();
// When it is rotated back to portrait
mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
@@ -300,4 +300,24 @@
verify(mQSTile).refreshState();
verify(mOtherTile, never()).refreshState();
}
+
+ @Test
+ public void configurationChange_onlySplitShadeConfigChanges_horizontalLayoutStatusUpdated() {
+ // Preconditions for horizontal layout
+ when(mMediaHost.getVisible()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ reset(mHorizontalLayoutListener);
+
+ // Only split shade status changes
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Horizontal layout is updated accordingly.
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener).run();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5eb9a98..e539705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,6 +6,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -52,6 +53,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var otherTile: QSTile
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var featureFlags: FeatureFlags
private lateinit var controller: QSPanelController
@@ -82,7 +84,8 @@
brightnessControllerFactory,
brightnessSliderFactory,
falsingManager,
- statusBarKeyguardViewManager
+ statusBarKeyguardViewManager,
+ featureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 2db58be..7c930b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -159,6 +159,32 @@
}
@Test
+ fun testTopPadding_notCombinedHeaders() {
+ qsPanel.setUsingCombinedHeaders(false)
+ val padding = 10
+ val paddingCombined = 100
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingTop).isEqualTo(padding)
+ }
+
+ @Test
+ fun testTopPadding_combinedHeaders() {
+ qsPanel.setUsingCombinedHeaders(true)
+ val padding = 10
+ val paddingCombined = 100
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
+ }
+
+ @Test
fun testSetSquishinessFraction_noCrash() {
qsPanel.addView(qsPanel.mTileLayout as View, 0)
qsPanel.addView(FrameLayout(context))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index a4a89a4..7a74b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -27,8 +27,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -42,8 +42,8 @@
private val viewsIdToRegister =
setOf(
- ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
- ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+ ViewIdToTranslate(START_VIEW_ID, Direction.START),
+ ViewIdToTranslate(END_VIEW_ID, Direction.END))
@Before
fun setup() {
@@ -66,41 +66,62 @@
}
@Test
- fun onTransition_oneMovesLeft() {
+ fun onTransition_oneMovesStartWithLTR() {
// GIVEN one view with a matching id
val view = View(context)
- whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
- moveAndValidate(listOf(view to LEFT))
+ moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_LTR)
}
@Test
- fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+ fun onTransition_oneMovesStartWithRTL() {
+ // GIVEN one view with a matching id
+ val view = View(context)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
+
+ whenever(parent.getLayoutDirection()).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_RTL)
+ }
+
+ @Test
+ fun onTransition_oneMovesStartAndOneMovesEndMultipleTimes() {
// GIVEN two views with a matching id
val leftView = View(context)
val rightView = View(context)
- whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
- whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(leftView)
+ whenever(parent.findViewById<View>(END_VIEW_ID)).thenReturn(rightView)
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
+ moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
}
- private fun moveAndValidate(list: List<Pair<View, Int>>) {
+ private fun moveAndValidate(list: List<Pair<View, Int>>, layoutDirection: Int) {
// Compare values as ints because -0f != 0f
// WHEN the transition starts
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0f)
+ val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ 1
+ } else {
+ -1
+ }
list.forEach { (view, direction) ->
- assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+ assertEquals(
+ (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
+ view.translationX.toInt()
+ )
}
// WHEN the transition progresses, translation is updated
progressProvider.onTransitionProgress(.5f)
list.forEach { (view, direction) ->
- assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+ assertEquals(
+ (-MAX_TRANSLATION / 2f * direction * rtlMultiplier).toInt(),
+ view.translationX.toInt()
+ )
}
// WHEN the transition ends, translation is completed
@@ -110,12 +131,12 @@
}
companion object {
- private val LEFT = Direction.LEFT.multiplier.toInt()
- private val RIGHT = Direction.RIGHT.multiplier.toInt()
+ private val START = Direction.START.multiplier.toInt()
+ private val END = Direction.END.multiplier.toInt()
private const val MAX_TRANSLATION = 42f
- private const val LEFT_VIEW_ID = 1
- private const val RIGHT_VIEW_ID = 2
+ private const val START_VIEW_ID = 1
+ private const val END_VIEW_ID = 2
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
deleted file mode 100644
index 5432a74..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.junit.Assert.assertFalse;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Verifies that particular sets of dependencies don't have dependencies on others. For example,
- * code managing notifications shouldn't directly depend on CentralSurfaces, since there are
- * platforms which want to manage notifications, but don't use CentralSurfaces.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NonPhoneDependencyTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotificationListContainer mListContainer;
- @Mock private RemoteInputController.Delegate mDelegate;
- @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
- @Mock private OnSettingsClickListener mOnSettingsClickListener;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- new Handler(TestableLooper.get(this).getLooper()));
- }
-
- @Ignore("Causes binder calls which fail")
- @Test
- public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
- mDependency.injectMockDependency(ShadeController.class);
- NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
- NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
- NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
- NotificationRemoteInputManager remoteInputManager =
- Dependency.get(NotificationRemoteInputManager.class);
- NotificationLockscreenUserManager lockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- gutsManager.setUpWithPresenter(mPresenter, mListContainer,
- mOnSettingsClickListener);
- notificationLogger.setUpWithContainer(mListContainer);
- mediaManager.setUpWithPresenter(mPresenter);
- remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
- mDelegate);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
-
- TestableLooper.get(this).processAllMessages();
- assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 853d1df..bdafa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -88,6 +89,8 @@
@Mock
private NotificationClickNotifier mClickNotifier;
@Mock
+ private OverviewProxyService mOverviewProxyService;
+ @Mock
private KeyguardManager mKeyguardManager;
@Mock
private DeviceProvisionedController mDeviceProvisionedController;
@@ -344,6 +347,7 @@
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
+ (() -> mOverviewProxyService),
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
new file mode 100644
index 0000000..16e2441
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
@@ -0,0 +1,321 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.mock
+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.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryMonitorTest : SysuiTestCase() {
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_plainNotification() {
+ val notification = createBasicNotification().build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+ val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = 0,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
+ val notification =
+ createBasicNotification()
+ .setCustomContentView(
+ RemoteViews(context.packageName, android.R.layout.list_content)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3384,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = true,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
+ val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
+ val notification =
+ createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = 444444,
+ largeIcon = 0,
+ extras = 3212,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_bigPictureStyle() {
+ val bigPicture =
+ Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
+ val bigPictureIcon =
+ Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val notification =
+ createBasicNotification()
+ .setStyle(
+ Notification.BigPictureStyle()
+ .bigPicture(bigPicture)
+ .bigLargeIcon(bigPictureIcon)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 4092,
+ bigPicture = bigPicture.bitmap.allocationByteCount,
+ extender = 0,
+ style = "BigPictureStyle",
+ styleIcon = bigPictureIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_callingStyle() {
+ val personIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val fakeIntent =
+ PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+ val notification =
+ createBasicNotification()
+ .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 4084,
+ bigPicture = 0,
+ extender = 0,
+ style = "CallStyle",
+ styleIcon = personIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_messagingStyle() {
+ val personIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+ val historicPersonIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
+ val historicPerson =
+ Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
+ val historicMessage =
+ Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
+
+ val notification =
+ createBasicNotification()
+ .setStyle(
+ Notification.MessagingStyle(person)
+ .addMessage(message)
+ .addHistoricMessage(historicMessage)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 5024,
+ bigPicture = 0,
+ extender = 0,
+ style = "MessagingStyle",
+ styleIcon =
+ personIcon.bitmap.allocationByteCount +
+ historicPersonIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_carExtender() {
+ val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
+ val extender = Notification.CarExtender().setLargeIcon(carIcon)
+ val notification = createBasicNotification().extend(extender).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3612,
+ bigPicture = 0,
+ extender = 556656,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_tvWearExtender() {
+ val tvExtender = Notification.TvExtender().setChannel("channel2")
+ val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
+ val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
+ val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3820,
+ bigPicture = 0,
+ extender = 388 + wearBackground.allocationByteCount,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ private fun createBasicNotification(): Notification.Builder {
+ val smallIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
+ val largeIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
+ return Notification.Builder(context)
+ .setSmallIcon(smallIcon)
+ .setLargeIcon(largeIcon)
+ .setContentTitle("This is a title")
+ .setContentText("This is content text.")
+ }
+
+ /** This will generate a nicer error message than comparing objects */
+ private fun assertNotificationObjectSizes(
+ memoryUse: NotificationMemoryUsage,
+ smallIcon: Int,
+ largeIcon: Int,
+ extras: Int,
+ bigPicture: Int,
+ extender: Int,
+ style: String?,
+ styleIcon: Int,
+ hasCustomView: Boolean
+ ) {
+ assertThat(memoryUse.packageName).isEqualTo("test_pkg")
+ assertThat(memoryUse.notificationId)
+ .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
+ assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
+ assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
+ assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
+ if (style == null) {
+ assertThat(memoryUse.objectUsage.style).isNull()
+ } else {
+ assertThat(memoryUse.objectUsage.style).isEqualTo(style)
+ }
+ assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
+ assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
+ }
+
+ private fun getUseObject(
+ singleItemUseList: List<NotificationMemoryUsage>
+ ): NotificationMemoryUsage {
+ assertThat(singleItemUseList).hasSize(1)
+ return singleItemUseList[0]
+ }
+
+ private fun createNMMWithNotifications(
+ notifications: List<Notification>
+ ): NotificationMemoryMonitor {
+ val notifPipeline: NotifPipeline = mock()
+ val notificationEntries =
+ notifications.map { n ->
+ NotificationEntryBuilder().setTag("test").setNotification(n).build()
+ }
+ whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
+ return NotificationMemoryMonitor(notifPipeline, mock())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 682ff1f..81b8e98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -32,6 +32,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.widget.NotificationActionListLayout;
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -142,4 +143,60 @@
verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
}
+
+ @Test
+ @UiThreadTest
+ public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
+ View mockContracted = mock(NotificationHeaderView.class);
+
+ View mockExpandedActions = mock(NotificationActionListLayout.class);
+ View mockExpanded = mock(NotificationHeaderView.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockExpandedActions);
+
+ View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+ View mockHeadsUp = mock(NotificationHeaderView.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockHeadsUpActions);
+
+ mView.setContractedChild(mockContracted);
+ mView.setExpandedChild(mockExpanded);
+ mView.setHeadsUpChild(mockHeadsUp);
+
+ mView.setRemoteInputVisible(true);
+
+ verify(mockContracted, times(0)).findViewById(0);
+ verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
+ View mockContracted = mock(NotificationHeaderView.class);
+
+ View mockExpandedActions = mock(NotificationActionListLayout.class);
+ View mockExpanded = mock(NotificationHeaderView.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockExpandedActions);
+
+ View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+ View mockHeadsUp = mock(NotificationHeaderView.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockHeadsUpActions);
+
+ mView.setContractedChild(mockContracted);
+ mView.setExpandedChild(mockExpanded);
+ mView.setHeadsUpChild(mockHeadsUp);
+
+ mView.setRemoteInputVisible(false);
+
+ verify(mockContracted, times(0)).findViewById(0);
+ verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
}
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 8be9eb5..1c9b0be 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
@@ -45,6 +45,7 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -127,6 +128,7 @@
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private ShadeTransitionController mShadeTransitionController;
+ @Mock private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -174,7 +176,8 @@
mJankMonitor,
mStackLogger,
mLogger,
- mNotificationStackSizeCalculator
+ mNotificationStackSizeCalculator,
+ mFeatureFlags
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
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 6ae021b..4353036 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
@@ -55,6 +55,7 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
+import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -179,6 +180,40 @@
}
@Test
+ public void testUpdateStackHeight_qsExpansionGreaterThanZero() {
+ final float expansionFraction = 0.2f;
+ final float overExpansion = 50f;
+
+ mStackScroller.setQsExpansionFraction(1f);
+ mAmbientState.setExpansionFraction(expansionFraction);
+ mAmbientState.setOverExpansion(overExpansion);
+ when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+
+ mStackScroller.setExpandedHeight(100f);
+
+ float expected = MathUtils.lerp(0, overExpansion,
+ BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansionFraction));
+ assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+ }
+
+ @Test
+ public void testUpdateStackHeight_qsExpansionZero() {
+ final float expansionFraction = 0.2f;
+ final float overExpansion = 50f;
+
+ mStackScroller.setQsExpansionFraction(0f);
+ mAmbientState.setExpansionFraction(expansionFraction);
+ mAmbientState.setOverExpansion(overExpansion);
+ when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+ mStackScroller.setExpandedHeight(100f);
+
+ float expected = MathUtils.lerp(0, overExpansion, expansionFraction);
+ assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+ }
+
+ @Test
public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
final float dozeAmount = 0.5f;
mAmbientState.setDozeAmount(dozeAmount);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 1305d79..4ea1c71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -18,10 +18,13 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -51,10 +54,15 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
/**
* Tests for {@link NotificationSwipeHelper}.
*/
@@ -74,7 +82,11 @@
private Runnable mFalsingCheck;
private FeatureFlags mFeatureFlags;
- @Rule public MockitoRule mockito = MockitoJUnit.rule();
+ private static final int FAKE_ROW_WIDTH = 20;
+ private static final int FAKE_ROW_HEIGHT = 20;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
@@ -444,8 +456,8 @@
doReturn(5f).when(mEvent).getRawX();
doReturn(10f).when(mEvent).getRawY();
- doReturn(20).when(mView).getWidth();
- doReturn(20).when(mView).getHeight();
+ doReturn(FAKE_ROW_WIDTH).when(mView).getWidth();
+ doReturn(FAKE_ROW_HEIGHT).when(mView).getHeight();
Answer answer = (Answer) invocation -> {
int[] arr = invocation.getArgument(0);
@@ -472,8 +484,8 @@
doReturn(5f).when(mEvent).getRawX();
doReturn(10f).when(mEvent).getRawY();
- doReturn(20).when(mNotificationRow).getWidth();
- doReturn(20).when(mNotificationRow).getActualHeight();
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getWidth();
+ doReturn(FAKE_ROW_HEIGHT).when(mNotificationRow).getActualHeight();
Answer answer = (Answer) invocation -> {
int[] arr = invocation.getArgument(0);
@@ -491,4 +503,56 @@
assertFalse("Touch is not within the view",
mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
}
+
+ @Test
+ public void testContentAlphaRemainsUnchangedWhenNotificationIsNotDismissible() {
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, false);
+
+ verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+ }
+
+ @Test
+ public void testContentAlphaRemainsUnchangedWhenFeatureFlagIsDisabled() {
+
+ // Returning true prevents alpha fade. In an unmocked scenario the callback is instantiated
+ // within NotificationStackScrollLayoutController and returns the inverted value of the
+ // feature flag
+ doReturn(true).when(mCallback).updateSwipeProgress(any(), anyBoolean(), anyFloat());
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, true);
+
+ verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+ }
+
+ @Test
+ public void testContentAlphaFadeAnimationSpecs() {
+ // The alpha fade should be linear from 1f to 0f as translation progresses from 0 to 60% of
+ // view-width, and 0f at translations higher than that.
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ List<Integer> translations = Arrays.asList(
+ -FAKE_ROW_WIDTH * 2,
+ -FAKE_ROW_WIDTH,
+ (int) (-FAKE_ROW_WIDTH * 0.3),
+ 0,
+ (int) (FAKE_ROW_WIDTH * 0.3),
+ (int) (FAKE_ROW_WIDTH * 0.6),
+ FAKE_ROW_WIDTH,
+ FAKE_ROW_WIDTH * 2);
+ List<Float> expectedAlphas = translations.stream().map(translation ->
+ mSwipeHelper.getSwipeAlpha(Math.abs((float) translation / FAKE_ROW_WIDTH)))
+ .collect(Collectors.toList());
+
+ for (Integer translation : translations) {
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, translation, true);
+ }
+
+ ArgumentCaptor<Float> capturedValues = ArgumentCaptor.forClass(Float.class);
+ verify(mNotificationRow, times(translations.size())).setContentAlpha(
+ capturedValues.capture());
+ assertEquals(expectedAlphas, capturedValues.getAllValues());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 9de9db1..996851e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -40,7 +40,6 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -73,7 +72,6 @@
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
- @Mock private KeyguardViewMediator mKeyguardViewMediator;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -101,7 +99,7 @@
mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
mBatteryController, mScrimController, () -> mBiometricUnlockController,
- mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
+ () -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
mAuthController, mNotificationIconAreaController);
@@ -132,19 +130,11 @@
verify(mStatusBarStateController).setIsDozing(eq(false));
}
-
@Test
public void testPulseWhileDozing_updatesScrimController() {
mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
mCentralSurfaces.showKeyguardImpl();
- // Keep track of callback to be able to stop the pulse
-// DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-// doAnswer(invocation -> {
-// pulseCallback[0] = invocation.getArgument(0);
-// return null;
-// }).when(mDozeScrimController).pulse(any(), anyInt());
-
// Starting a pulse should change the scrim controller to the pulsing state
mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
@Override
@@ -210,4 +200,17 @@
}
}
}
+
+ @Test
+ public void testStopPulsing_setPendingPulseToFalse() {
+ // GIVEN a pending pulse
+ mDozeServiceHost.setPulsePending(true);
+
+ // WHEN pulsing is stopped
+ mDozeServiceHost.stopPulsing();
+
+ // THEN isPendingPulse=false, pulseOutNow is called
+ assertFalse(mDozeServiceHost.isPulsePending());
+ verify(mDozeScrimController).pulseOutNow();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index cfaa470..6ec5cf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -47,6 +47,7 @@
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.battery.BatteryMeterViewController;
@@ -123,6 +124,7 @@
private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
+ @Mock private KeyguardLogger mLogger;
private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -172,7 +174,8 @@
mStatusBarUserInfoTracker,
mSecureSettings,
mCommandQueue,
- mFakeExecutor
+ mFakeExecutor,
+ mLogger
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 34399b8..9c56c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -80,6 +81,7 @@
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -123,12 +125,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
contextProvider,
darkIconDispatcher);
}
@@ -169,6 +173,7 @@
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
contextProvider);
}
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 f52bfca..5a12fcb1 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
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -23,11 +25,9 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -528,25 +528,10 @@
verify(mCentralSurfaces).setBouncerShowingOverDream(false);
}
-
@Test
- public void testSetDozing_bouncerShowing_Dozing() {
- clearInvocations(mBouncer);
- when(mBouncer.isShowing()).thenReturn(true);
- doAnswer(invocation -> {
- when(mBouncer.isShowing()).thenReturn(false);
- return null;
- }).when(mBouncer).hide(false);
- mStatusBarKeyguardViewManager.onDozingChanged(true);
- verify(mBouncer, times(1)).hide(false);
- }
-
- @Test
- public void testSetDozing_bouncerShowing_notDozing() {
- mStatusBarKeyguardViewManager.onDozingChanged(true);
- when(mBouncer.isShowing()).thenReturn(true);
- clearInvocations(mBouncer);
- mStatusBarKeyguardViewManager.onDozingChanged(false);
- verify(mBouncer, times(1)).hide(false);
+ public void flag_off_DoesNotCallBouncerInteractor() {
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(false);
+ verify(mBouncerInteractor, never()).hide();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
new file mode 100644
index 0000000..0d15268
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+ private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+
+ private val _activeMobileDataSubscriptionId =
+ MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+ private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
+ override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
+ return subIdFlows[subId]
+ ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ }
+
+ fun setSubscriptions(subs: List<SubscriptionInfo>) {
+ _subscriptionsFlow.value = subs
+ }
+
+ fun setActiveMobileDataSubscriptionId(subId: Int) {
+ _activeMobileDataSubscriptionId.value = subId
+ }
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
+ val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
+ subscription.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 0000000..6c495c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+class FakeUserSetupRepository : UserSetupRepository {
+ private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+
+ fun setUserSetup(setup: Boolean) {
+ _isUserSetup.value = setup
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
new file mode 100644
index 0000000..316b795
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
@@ -0,0 +1,360 @@
+/*
+ * 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.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+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.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileSubscriptionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileSubscriptionRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ MobileSubscriptionRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_default() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly_toggles() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ callback.onServiceStateChanged(serviceState)
+ serviceState.isEmergencyOnly = false
+ callback.onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
+ val strength = signalStrength(1, 2, true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest?.isGsm).isEqualTo(true)
+ assertThat(latest?.primaryLevel).isEqualTo(1)
+ assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataActivity() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(3)
+
+ assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_carrierNetworkChange() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_displayInfo() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DisplayInfoListener>()
+ val ti = mock<TelephonyDisplayInfo>()
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.displayInfo).isEqualTo(ti)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ val state1 = underTest.getFlowForSubId(SUB_1_ID)
+ val state2 = underTest.getFlowForSubId(SUB_1_ID)
+
+ assertThat(state1).isEqualTo(state2)
+ }
+
+ @Test
+ fun testFlowForSubId_isRemovedAfterFinish() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+
+ // Start collecting on some flow
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ // There should be once cached flow now
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+
+ // When the job is canceled, the cache should be cleared
+ job.cancel()
+
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
+ getTelephonyCallbackForType()
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ /** Convenience constructor for SignalStrength */
+ private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..91c233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class UserSetupRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: UserSetupRepository
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ UserSetupRepositoryImpl(
+ deviceProvisionedController,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testUserSetup_defaultFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testUserSetup_updatesOnChange() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ val callback = getDeviceProvisionedListener()
+ callback.onUserSetupChanged()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+ val captor = argumentCaptor<DeviceProvisionedListener>()
+ verify(deviceProvisionedController).addCallback(captor.capture())
+ return captor.value!!
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 0000000..8ec68f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.domain.interactor
+
+import android.telephony.CellSignalStrength
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor : MobileIconInteractor {
+ private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+ override val iconGroup = _iconGroup
+
+ private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+ override val isEmergencyOnly = _isEmergencyOnly
+
+ private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val level = _level
+
+ private val _numberOfLevels = MutableStateFlow<Int>(4)
+ override val numberOfLevels = _numberOfLevels
+
+ private val _cutOut = MutableStateFlow<Boolean>(false)
+ override val cutOut = _cutOut
+
+ fun setIconGroup(group: SignalIcon.MobileIconGroup) {
+ _iconGroup.value = group
+ }
+
+ fun setIsEmergencyOnly(emergency: Boolean) {
+ _isEmergencyOnly.value = emergency
+ }
+
+ fun setLevel(level: Int) {
+ _level.value = level
+ }
+
+ fun setNumberOfLevels(num: Int) {
+ _numberOfLevels.value = num
+ }
+
+ fun setCutOut(cutOut: Boolean) {
+ _cutOut.value = cutOut
+ }
+}
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
new file mode 100644
index 0000000..2f07d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.domain.interactor
+
+import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class MobileIconInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconInteractor
+ private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
+ private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+
+ @Before
+ fun setUp() {
+ underTest = MobileIconInteractorImpl(sub1Flow)
+ }
+
+ @Test
+ fun gsm_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = true),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ job.cancel()
+ }
+
+ @Test
+ fun gsm_usesGsmLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = true,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = false),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_usesCdmaLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = false,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val GSM_LEVEL = 1
+ private const val CDMA_LEVEL = 2
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
new file mode 100644
index 0000000..89ad9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconsInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconsInteractor
+ private val userSetupRepository = FakeUserSetupRepository()
+ private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ MobileIconsInteractor(
+ subscriptionsRepository,
+ carrierConfigTracker,
+ userSetupRepository,
+ )
+ }
+
+ @After fun tearDown() {}
+
+ @Test
+ fun filteredSubscriptions_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val SUB_3_ID = 3
+ private val SUB_3_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_3_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+
+ private const val SUB_4_ID = 4
+ private val SUB_4_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_4_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
new file mode 100644
index 0000000..b374abb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconViewModel
+ private val interactor = FakeMobileIconInteractor()
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ interactor.apply {
+ setLevel(1)
+ setCutOut(false)
+ setIconGroup(TelephonyIcons.THREE_G)
+ setIsEmergencyOnly(false)
+ setNumberOfLevels(4)
+ }
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+ }
+
+ @Test
+ fun iconId_correctLevel_notCutout() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 921b7ef..7cb2806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -61,6 +61,8 @@
@Mock
private lateinit var powerManager: PowerManager
+ private var shouldIgnoreViewRemoval: Boolean = false
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -205,6 +207,26 @@
verify(windowManager, never()).removeView(any())
}
+ @Test
+ fun removeView_shouldIgnoreRemovalFalse_viewRemoved() {
+ shouldIgnoreViewRemoval = false
+ underTest.displayView(getState())
+
+ underTest.removeView("reason")
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun removeView_shouldIgnoreRemovalTrue_viewNotRemoved() {
+ shouldIgnoreViewRemoval = true
+ underTest.displayView(getState())
+
+ underTest.removeView("reason")
+
+ verify(windowManager, never()).removeView(any())
+ }
+
private fun getState(name: String = "name") = ViewInfo(name)
private fun getConfigurationListener(): ConfigurationListener {
@@ -240,6 +262,10 @@
super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo
}
+
+ override fun shouldIgnoreViewRemoval(removalReason: String): Boolean {
+ return shouldIgnoreViewRemoval
+ }
}
inner class ViewInfo(val name: String) : TemporaryViewInfo {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 3434376..c254358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,14 +16,23 @@
package com.android.systemui.wallpapers;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.intThat;
import android.app.WallpaperManager;
import android.content.Context;
@@ -31,17 +40,25 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
import org.junit.Before;
@@ -56,7 +73,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-@Ignore
public class ImageWallpaperTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
@@ -66,44 +82,86 @@
private static final int DISPLAY_HEIGHT = 1080;
@Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+ @Mock
+ private DisplayManager mDisplayManager;
+ @Mock
+ private WallpaperManager mWallpaperManager;
+ @Mock
private SurfaceHolder mSurfaceHolder;
@Mock
+ private Surface mSurface;
+ @Mock
private Context mMockContext;
+
@Mock
private Bitmap mWallpaperBitmap;
+ private int mBitmapWidth = 1;
+ private int mBitmapHeight = 1;
+
@Mock
private Handler mHandler;
@Mock
private FeatureFlags mFeatureFlags;
+ FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
+ FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+
private CountDownLatch mEventCountdown;
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mEventCountdown = new CountDownLatch(1);
+ //mEventCountdown = new CountDownLatch(1);
- WallpaperManager wallpaperManager = mock(WallpaperManager.class);
+ // set up window manager
+ when(mWindowMetrics.getBounds()).thenReturn(
+ new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+
+ // set up display manager
+ doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
+ when(mMockContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+ // set up bitmap
+ when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
+ when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
+ when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
+
+ // set up wallpaper manager
+ when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
+ new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+ when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+ when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
+
+ // set up surface
+ when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
+ doNothing().when(mSurface).hwuiDestroy();
+
+ // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
Resources resources = mock(Resources.class);
-
- when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
- when(mMockContext.getResources()).thenReturn(resources);
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
-
+ when(mMockContext.getResources()).thenReturn(resources);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
displayInfo.logicalHeight = DISPLAY_HEIGHT;
when(mMockContext.getDisplay()).thenReturn(
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
+ }
- when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
- when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
- when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+ mBitmapWidth = bitmapWidth;
+ mBitmapHeight = bitmapHeight;
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFeatureFlags) {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
@Override
public Engine onCreateEngine() {
return new GLEngine(mHandler) {
@@ -130,6 +188,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_normal() {
// Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
@@ -140,6 +199,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_low_resolution() {
// Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
@@ -150,6 +210,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_too_small() {
// Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
// Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
@@ -166,8 +227,7 @@
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
- when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
- when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
+ setBitmapDimensions(bmpWidth, bmpHeight);
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
doReturn(renderer).when(engineSpy).getRendererInstance();
@@ -177,4 +237,116 @@
assertWithMessage("setFixedSizeAllowed should have been called.").that(
mEventCountdown.getCount()).isEqualTo(0);
}
+
+
+ private ImageWallpaper createImageWallpaperCanvas() {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+ @Override
+ public Engine onCreateEngine() {
+ return new CanvasEngine() {
+ @Override
+ public Context getDisplayContext() {
+ return mMockContext;
+ }
+
+ @Override
+ public SurfaceHolder getSurfaceHolder() {
+ return mSurfaceHolder;
+ }
+
+ @Override
+ public void setFixedSizeAllowed(boolean allowed) {
+ super.setFixedSizeAllowed(allowed);
+ assertWithMessage("mFixedSizeAllowed should be true").that(
+ allowed).isTrue();
+ }
+ };
+ }
+ };
+ }
+
+ private ImageWallpaper.CanvasEngine getSpyEngine() {
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
+ doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+ doAnswer(invocation -> {
+ ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
+ return null;
+ }).when(spyEngine).recomputeColorExtractorMiniBitmap();
+ return spyEngine;
+ }
+
+ @Test
+ public void testMinSurface() {
+
+ // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+ testMinSurfaceHelper(8, 8);
+ testMinSurfaceHelper(100, 2000);
+ testMinSurfaceHelper(200, 1);
+ testMinSurfaceHelper(0, 1);
+ testMinSurfaceHelper(1, 0);
+ testMinSurfaceHelper(0, 0);
+ }
+
+ private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ engine.onCreate(mSurfaceHolder);
+
+ verify(mSurfaceHolder, times(1)).setFixedSize(
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
+ }
+
+ @Test
+ public void testZeroBitmap() {
+ // test that a frame is never drawn with a 0 bitmap
+ testZeroBitmapHelper(0, 1);
+ testZeroBitmapHelper(1, 0);
+ testZeroBitmapHelper(0, 0);
+ }
+
+ private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ verify(spyEngine, never()).drawFrameOnCanvas(any());
+ }
+
+ @Test
+ public void testLoadDrawAndUnloadBitmap() {
+ setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
+
+ ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+
+ int n = 0;
+ while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+ n++;
+ assertThat(n).isAtMost(10);
+ mFakeBackgroundExecutor.runNextReady();
+ mFakeMainExecutor.runNextReady();
+ mFakeSystemClock.advanceTime(1000);
+ }
+
+ verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+ assertThat(spyEngine.isBitmapLoaded()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
deleted file mode 100644
index 93f4f82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
+++ /dev/null
@@ -1,133 +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.wallpapers.canvas;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.intThat;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.DisplayInfo;
-import android.view.SurfaceHolder;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ImageCanvasWallpaperRendererTest extends SysuiTestCase {
-
- private static final int MOBILE_DISPLAY_WIDTH = 720;
- private static final int MOBILE_DISPLAY_HEIGHT = 1600;
-
- @Mock
- private SurfaceHolder mMockSurfaceHolder;
-
- @Mock
- private DisplayInfo mMockDisplayInfo;
-
- @Mock
- private Bitmap mMockBitmap;
-
- @Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
- MockitoAnnotations.initMocks(this);
- }
-
- private void setDimensions(
- int bitmapWidth, int bitmapHeight,
- int displayWidth, int displayHeight) {
- when(mMockBitmap.getWidth()).thenReturn(bitmapWidth);
- when(mMockBitmap.getHeight()).thenReturn(bitmapHeight);
- mMockDisplayInfo.logicalWidth = displayWidth;
- mMockDisplayInfo.logicalHeight = displayHeight;
- }
-
- private void testMinDimensions(
- int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- renderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, times(1)).setFixedSize(
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_WIDTH)),
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_HEIGHT)));
- }
-
- @Test
- public void testMinSurface() {
- // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
- testMinDimensions(8, 8);
-
- testMinDimensions(100, 2000);
-
- testMinDimensions(200, 1);
- }
-
- private void testZeroDimensions(int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- ImageCanvasWallpaperRenderer spyRenderer = spy(renderer);
- spyRenderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, never()).setFixedSize(anyInt(), anyInt());
- verify(spyRenderer, never()).drawWallpaperWithCanvas(any());
- }
-
- @Test
- public void testZeroBitmap() {
- // test that updateSurfaceSize is not called with a bitmap of width 0 or height 0
- testZeroDimensions(
- 0, 1
- );
-
- testZeroDimensions(1, 0
- );
-
- testZeroDimensions(0, 0
- );
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
new file mode 100644
index 0000000..76bff1d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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.wallpapers.canvas;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WallpaperColorExtractorTest extends SysuiTestCase {
+ private static final int LOW_BMP_WIDTH = 128;
+ private static final int LOW_BMP_HEIGHT = 128;
+ private static final int HIGH_BMP_WIDTH = 3000;
+ private static final int HIGH_BMP_HEIGHT = 4000;
+ private static final int VERY_LOW_BMP_WIDTH = 1;
+ private static final int VERY_LOW_BMP_HEIGHT = 1;
+ private static final int DISPLAY_WIDTH = 1920;
+ private static final int DISPLAY_HEIGHT = 1080;
+
+ private static final int PAGES_LOW = 4;
+ private static final int PAGES_HIGH = 7;
+
+ private static final int MIN_AREAS = 4;
+ private static final int MAX_AREAS = 10;
+
+ private int mMiniBitmapWidth;
+ private int mMiniBitmapHeight;
+
+ @Mock
+ private Executor mBackgroundExecutor;
+
+ private int mColorsProcessed;
+ private int mMiniBitmapUpdatedCount;
+ private int mActivatedCount;
+ private int mDeactivatedCount;
+
+ @Before
+ public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mBackgroundExecutor).execute(any(Runnable.class));
+ }
+
+ private void resetCounters() {
+ mColorsProcessed = 0;
+ mMiniBitmapUpdatedCount = 0;
+ mActivatedCount = 0;
+ mDeactivatedCount = 0;
+ }
+
+ private Bitmap getMockBitmap(int width, int height) {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getWidth()).thenReturn(width);
+ when(bitmap.getHeight()).thenReturn(height);
+ return bitmap;
+ }
+
+ private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+
+ WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ assertThat(regions.size()).isEqualTo(colors.size());
+ mColorsProcessed += regions.size();
+ }
+
+ @Override
+ public void onMiniBitmapUpdated() {
+ mMiniBitmapUpdatedCount++;
+ }
+
+ @Override
+ public void onActivated() {
+ mActivatedCount++;
+ }
+
+ @Override
+ public void onDeactivated() {
+ mDeactivatedCount++;
+ }
+ });
+ WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+
+ doAnswer(invocation -> {
+ mMiniBitmapWidth = invocation.getArgument(1);
+ mMiniBitmapHeight = invocation.getArgument(2);
+ return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
+ }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+
+ doAnswer(invocation -> getMockBitmap(
+ invocation.getArgument(1),
+ invocation.getArgument(2)))
+ .when(spyWallpaperColorExtractor)
+ .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+ doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
+ .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+
+ return spyWallpaperColorExtractor;
+ }
+
+ private RectF randomArea() {
+ float width = (float) Math.random();
+ float startX = (float) (Math.random() * (1 - width));
+ float height = (float) Math.random();
+ float startY = (float) (Math.random() * (1 - height));
+ return new RectF(startX, startY, startX + width, startY + height);
+ }
+
+ private List<RectF> listOfRandomAreas(int min, int max) {
+ int nAreas = randomBetween(min, max);
+ List<RectF> result = new ArrayList<>();
+ for (int i = 0; i < nAreas; i++) {
+ result.add(randomArea());
+ }
+ return result;
+ }
+
+ private int randomBetween(int minIncluded, int maxIncluded) {
+ return (int) (Math.random() * ((maxIncluded - minIncluded) + 1)) + minIncluded;
+ }
+
+ /**
+ * Test that for bitmaps of random dimensions, the mini bitmap is always created
+ * with either a width <= SMALL_SIDE or a height <= SMALL_SIDE
+ */
+ @Test
+ public void testMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
+ int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for bitmaps with both width and height <= SMALL_SIDE,
+ * the mini bitmap is always created with both width and height <= SMALL_SIDE
+ */
+ @Test
+ public void testSmallMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
+ int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for a new color extractor with information
+ * (number of pages, display dimensions, wallpaper bitmap) given in random order,
+ * the colors are processed and all the callbacks are properly executed.
+ */
+ @Test
+ public void testNewColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ regions));
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mActivatedCount).isEqualTo(1);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that the method removeLocalColorAreas behaves properly and does not call
+ * the onDeactivated callback unless all color areas are removed.
+ */
+ @Test
+ public void testRemoveColors() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mDeactivatedCount).isEqualTo(0);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that if we change some information (wallpaper bitmap, number of pages),
+ * the colors are correctly recomputed.
+ * Test that if we remove some color areas in the middle of the process,
+ * only the remaining areas are recomputed.
+ */
+ @Test
+ public void testRecomputeColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ int nPages = PAGES_LOW;
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyWallpaperColorExtractor.onPageChanged(nPages);
+ spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+
+ int nSimulations = 20;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+
+ // verify that if we remove some regions, they are not recomputed after other changes
+ if (i == nSimulations / 2) {
+ regions.removeAll(regions2);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ }
+
+ if (Math.random() >= 0.5) {
+ int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
+ if (nPagesNew == nPages) continue;
+ nPages = nPagesNew;
+ spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ } else {
+ Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ }
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+ }
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testCleanUp() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ doNothing().when(bitmap).recycle();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ spyWallpaperColorExtractor.cleanUp();
+ spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ assertThat(mColorsProcessed).isEqualTo(0);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index c83189d..fa3cc99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -84,9 +84,14 @@
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
- mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
- mock(Executor.class), mock(DumpManager.class),
- mock(BroadcastDispatcherLogger.class), mock(UserTracker.class));
+ mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
+ mContext,
+ mContext.getMainExecutor(),
+ mock(Looper.class),
+ mock(Executor.class),
+ mock(DumpManager.class),
+ mock(BroadcastDispatcherLogger.class),
+ mock(UserTracker.class));
mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
Instrumentation inst = spy(mRealInstrumentation);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index bb646f0..52e0c982 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -32,16 +32,18 @@
class FakeBroadcastDispatcher(
context: SysuiTestableContext,
- looper: Looper,
- executor: Executor,
+ mainExecutor: Executor,
+ broadcastRunningLooper: Looper,
+ broadcastRunningExecutor: Executor,
dumpManager: DumpManager,
logger: BroadcastDispatcherLogger,
userTracker: UserTracker
) :
BroadcastDispatcher(
context,
- looper,
- executor,
+ mainExecutor,
+ broadcastRunningLooper,
+ broadcastRunningExecutor,
dumpManager,
logger,
userTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2be67ed..23c7a61 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -70,6 +70,10 @@
}
@Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ }
+
+ @Override
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 2ab28c6..043aff6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -203,6 +203,6 @@
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
-private const val SPRING_STIFFNESS = 200.0f
+private const val SPRING_STIFFNESS = 600.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index e9dd836..8d878af 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1831,7 +1831,7 @@
if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
|| mWindowManager.isKeyguardShowingAndNotOccluded()
|| !mPowerManager.isInteractive())) {
- Sandman.startDreamWhenDockedIfAppropriate(getContext());
+ mInjector.startDreamWhenDockedIfAppropriate(getContext());
}
}
@@ -2145,5 +2145,9 @@
public int getCallingUid() {
return Binder.getCallingUid();
}
+
+ public void startDreamWhenDockedIfAppropriate(Context context) {
+ Sandman.startDreamWhenDockedIfAppropriate(context);
+ }
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 6b731c3..c128b5e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -89,6 +89,7 @@
import android.os.UserManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -3100,7 +3101,7 @@
*/
if (!checkKeyIntent(
Binder.getCallingUid(),
- intent)) {
+ result)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"invalid intent in bundle returned");
return;
@@ -3519,7 +3520,7 @@
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
if (!checkKeyIntent(
Binder.getCallingUid(),
- intent)) {
+ result)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"invalid intent in bundle returned");
return;
@@ -4870,7 +4871,13 @@
* into launching arbitrary intents on the device via by tricking to click authenticator
* supplied entries in the system Settings app.
*/
- protected boolean checkKeyIntent(int authUid, Intent intent) {
+ protected boolean checkKeyIntent(int authUid, Bundle bundle) {
+ if (!checkKeyIntentParceledCorrectly(bundle)) {
+ EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
+ return false;
+ }
+
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
// Explicitly set an empty ClipData to ensure that we don't offer to
// promote any Uris contained inside for granting purposes
if (intent.getClipData() == null) {
@@ -4905,6 +4912,25 @@
}
}
+ /**
+ * Simulate the client side's deserialization of KEY_INTENT value, to make sure they don't
+ * violate our security policy.
+ *
+ * In particular we want to make sure the Authenticator doesn't trick users
+ * into launching arbitrary intents on the device via exploiting any other Parcel read/write
+ * mismatch problems.
+ */
+ private boolean checkKeyIntentParceledCorrectly(Bundle bundle) {
+ Parcel p = Parcel.obtain();
+ p.writeBundle(bundle);
+ p.setDataPosition(0);
+ Bundle simulateBundle = p.readBundle();
+ p.recycle();
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+ return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+ Intent.class)));
+ }
+
private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
String className = activityInfo.name;
return "android".equals(activityInfo.packageName) &&
@@ -5051,7 +5077,7 @@
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
if (!checkKeyIntent(
Binder.getCallingUid(),
- intent)) {
+ result)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"invalid intent in bundle returned");
return;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0d08db9..736914a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1365,7 +1365,8 @@
break;
case MSG_II_SET_HEARING_AID_VOLUME:
synchronized (mDeviceStateLock) {
- mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+ mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2,
+ mDeviceInventory.isHearingAidConnected());
}
break;
case MSG_II_SET_LE_AUDIO_OUT_VOLUME: {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ee0d79f..35da73e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -377,7 +377,8 @@
makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+ streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
+ btInfo.mAudioSystemDevice,
"onSetBtActiveDevice");
}
break;
@@ -1161,6 +1162,22 @@
.record();
}
+ /**
+ * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
+ * Visibility by APM plays no role
+ * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
+ */
+ boolean isHearingAidConnected() {
+ synchronized (mDevicesLock) {
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
int volumeIndex, int device, String eventSource) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0b6b890..745555c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,10 +41,12 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
@@ -1185,6 +1187,8 @@
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
@@ -1202,7 +1206,7 @@
mPlaybackMonitor =
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
device -> onMuteAwaitConnectionTimeout(device));
- mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
+ mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1308,6 +1312,7 @@
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+ intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
Context.RECEIVER_EXPORTED);
@@ -1927,13 +1932,7 @@
if (state == AudioService.CONNECTION_STATE_CONNECTED) {
// DEVICE_OUT_HDMI is now connected
if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
if (isPlatformTelevision()) {
@@ -3822,8 +3821,9 @@
}
private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
+ private AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
- private final IPlaybackConfigDispatcher mVoicePlaybackActivityMonitor =
+ private final IPlaybackConfigDispatcher mPlaybackActivityMonitor =
new IPlaybackConfigDispatcher.Stub() {
@Override
public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
@@ -3836,19 +3836,26 @@
private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
boolean voiceActive = false;
+ boolean mediaActive = false;
for (AudioPlaybackConfiguration config : configs) {
final int usage = config.getAudioAttributes().getUsage();
- if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
- || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
- && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (!config.isActive()) {
+ continue;
+ }
+ if (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+ || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
voiceActive = true;
- break;
+ }
+ if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+ mediaActive = true;
}
}
if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
updateHearingAidVolumeOnVoiceActivityUpdate();
}
-
+ if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
+ scheduleMusicActiveCheck();
+ }
// Update playback active state for all apps in audio mode stack.
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
// and request an audio mode update immediately. Upon any other change, queue the message
@@ -4011,7 +4018,7 @@
}
}
- private void setLeAudioVolumeOnModeUpdate(int mode) {
+ private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
switch (mode) {
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
@@ -4025,8 +4032,6 @@
return;
}
- int streamType = getBluetoothContextualVolumeStream(mode);
-
// Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
// (See AudioDeviceBroker#createBtDeviceInfo())
int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
@@ -4037,6 +4042,7 @@
+ index + " maxIndex=" + maxIndex + " streamType=" + streamType);
}
mDeviceBroker.postSetLeAudioVolumeIndex(index, maxIndex, streamType);
+ mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "setLeAudioVolumeOnModeUpdate");
}
private void setStreamVolume(int streamType, int index, int flags,
@@ -5417,7 +5423,7 @@
// Forcefully set LE audio volume as a workaround, since the value of 'device'
// is not DEVICE_OUT_BLE_* even when BLE is connected.
- setLeAudioVolumeOnModeUpdate(mode);
+ setLeAudioVolumeOnModeUpdate(mode, streamType, device);
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
@@ -6032,30 +6038,52 @@
return mContentResolver;
}
+ private void scheduleMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ cancelMusicActiveCheck();
+ mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+ new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime()
+ + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+ }
+ }
+
+ private void cancelMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ if (mMusicActiveIntent != null) {
+ mAlarmManager.cancel(mMusicActiveIntent);
+ mMusicActiveIntent = null;
+ }
+ }
+ }
private void onCheckMusicActive(String caller) {
synchronized (mSafeMediaVolumeStateLock) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-
- if (mSafeMediaVolumeDevices.contains(device)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ if (mSafeMediaVolumeDevices.contains(device)
+ && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+ scheduleMusicActiveCheck();
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
- && (index > safeMediaVolumeIndex(device))) {
+ if (index > safeMediaVolumeIndex(device)) {
// Approximate cumulative active music time
- mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+ long curTimeMs = SystemClock.elapsedRealtime();
+ if (mLastMusicActiveTimeMs != 0) {
+ mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+ }
+ mLastMusicActiveTimeMs = curTimeMs;
+ Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
setSafeMediaVolumeEnabled(true, caller);
mMusicActiveMs = 0;
}
saveMusicActiveMs();
}
+ } else {
+ cancelMusicActiveCheck();
+ mLastMusicActiveTimeMs = 0;
}
}
}
@@ -6124,6 +6152,7 @@
} else {
// We have existing playback time recorded, already confirmed.
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ mLastMusicActiveTimeMs = 0;
}
}
} else {
@@ -8633,13 +8662,7 @@
@VisibleForTesting
public void checkMusicActive(int deviceType, String caller) {
if (mSafeMediaVolumeDevices.contains(deviceType)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
}
@@ -8764,6 +8787,8 @@
suspendedPackages[i], suspendedUids[i]);
}
}
+ } else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
+ onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
}
}
} // end class AudioServiceBroadcastReceiver
@@ -9709,12 +9734,20 @@
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
// automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
private int mMusicActiveMs;
+ private long mLastMusicActiveTimeMs = 0;
+ private PendingIntent mMusicActiveIntent = null;
+ private AlarmManager mAlarmManager;
+
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed
// check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
+ private static final String ACTION_CHECK_MUSIC_ACTIVE =
+ AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+ private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
private int safeMediaVolumeIndex(int device) {
if (!mSafeMediaVolumeDevices.contains(device)) {
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -9736,14 +9769,9 @@
} else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
mMusicActiveMs = 1; // nonzero = confirmed
+ mLastMusicActiveTimeMs = 0;
saveMusicActiveMs();
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
}
}
@@ -9785,7 +9813,9 @@
public void disableSafeMediaVolume(String callingPackage) {
enforceVolumeController("disable the safe media volume");
synchronized (mSafeMediaVolumeStateLock) {
+ final long identity = Binder.clearCallingIdentity();
setSafeMediaVolumeEnabled(false, callingPackage);
+ Binder.restoreCallingIdentity(identity);
if (mPendingVolumeCommand != null) {
onSetStreamVolume(mPendingVolumeCommand.mStreamType,
mPendingVolumeCommand.mIndex,
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index d0f5470..6cd42f8 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -424,7 +424,8 @@
mLeAudio.setVolume(volume);
}
- /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
+ /*package*/ synchronized void setHearingAidVolume(int index, int streamType,
+ boolean isHeadAidConnected) {
if (mHearingAid == null) {
if (AudioService.DEBUG_VOL) {
Log.i(TAG, "setHearingAidVolume: null mHearingAid");
@@ -441,8 +442,11 @@
Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+ index + " gain=" + gainDB);
}
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
- AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+ // do not log when hearing aid is not connected to avoid confusion when reading dumpsys
+ if (isHeadAidConnected) {
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+ }
mHearingAid.setVolume(gainDB);
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a7d3729..40e28da 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -592,10 +592,14 @@
}
pw.println();
+ pw.println(" mAmbientBrightnessThresholds=");
mAmbientBrightnessThresholds.dump(pw);
+ pw.println(" mScreenBrightnessThresholds=");
mScreenBrightnessThresholds.dump(pw);
+ pw.println(" mScreenBrightnessThresholdsIdle=");
mScreenBrightnessThresholdsIdle.dump(pw);
- mScreenBrightnessThresholdsIdle.dump(pw);
+ pw.println(" mAmbientBrightnessThresholdsIdle=");
+ mAmbientBrightnessThresholdsIdle.dump(pw);
}
private String configStateToString(int state) {
@@ -860,6 +864,7 @@
Slog.d(TAG, "updateAmbientLux: "
+ ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ "mAmbientLux=" + mAmbientLux);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4165186..1ae37bb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -27,6 +27,7 @@
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.Spline;
import android.view.DisplayAddress;
@@ -51,7 +52,7 @@
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.Thresholds;
+import com.android.server.display.config.ThresholdPoint;
import com.android.server.display.config.XmlParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -188,42 +189,153 @@
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
* <ambientLightHorizonShort>2001</ambientLightHorizonShort>
*
- * <displayBrightnessChangeThresholds> // Thresholds for screen changes
- * <brighteningThresholds> // Thresholds for active mode brightness changes.
- * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
- * </darkeningThresholds>
- * </displayBrightnessChangeThresholds>
- *
- * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
- * <brighteningThresholds> // Thresholds for active mode brightness changes.
- * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
- * </darkeningThresholds>
- * </ambientBrightnessChangeThresholds>
- *
- * <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
- * <brighteningThresholds> // Thresholds for idle mode brightness changes.
- * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
- * </darkeningThresholds>
- * </displayBrightnessChangeThresholdsIdle>
- *
- * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
- * <brighteningThresholds> // Thresholds for idle mode brightness changes.
- * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
- * </darkeningThresholds>
- * </ambientBrightnessChangeThresholdsIdle>
- *
+ * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
+ * <brighteningThresholds>
+ * // Minimum change needed in ambient brightness to brighten screen.
+ * <minimum>10</minimum>
+ * // Percentage increase of lux needed to increase the screen brightness at a lux range
+ * // above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>13</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>100</threshold><percentage>14</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>200</threshold><percentage>15</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+* // Minimum change needed in ambient brightness to darken screen.
+ * <minimum>30</minimum>
+ * // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ * // above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>15</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>300</threshold><percentage>16</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>400</threshold><percentage>17</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholds>
+ * <displayBrightnessChangeThresholds> // Thresholds for screen brightness changes
+ * <brighteningThresholds>
+ * // Minimum change needed in screen brightness to brighten screen.
+ * <minimum>0.1</minimum>
+ * // Percentage increase of screen brightness needed to increase the screen brightness
+ * // at a lux range above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold>
+ * <percentage>9</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.10</threshold>
+ * <percentage>10</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.20</threshold>
+ * <percentage>11</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in screen brightness to darken screen.
+ * <minimum>0.3</minimum>
+ * // Percentage increase of screen brightness needed to decrease the screen brightness
+ * // at a lux range above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>11</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.11</threshold><percentage>12</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.21</threshold><percentage>13</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholds>
+ * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
+ * <brighteningThresholds>
+ * // Minimum change needed in ambient brightness to brighten screen in idle mode
+ * <minimum>20</minimum>
+ * // Percentage increase of lux needed to increase the screen brightness at a lux range
+ * // above the specified threshold whilst in idle mode.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>21</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>500</threshold><percentage>22</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>600</threshold><percentage>23</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in ambient brightness to darken screen in idle mode
+ * <minimum>40</minimum>
+ * // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ * // above the specified threshold whilst in idle mode.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>23</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>700</threshold><percentage>24</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>800</threshold><percentage>25</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholdsIdle>
+ * <displayBrightnessChangeThresholdsIdle> // Thresholds for idle screen brightness changes
+ * <brighteningThresholds>
+ * // Minimum change needed in screen brightness to brighten screen in idle mode
+ * <minimum>0.2</minimum>
+ * // Percentage increase of screen brightness needed to increase the screen brightness
+ * // at a lux range above the specified threshold whilst in idle mode
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>17</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.12</threshold><percentage>18</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.22</threshold><percentage>19</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in screen brightness to darken screen in idle mode
+ * <minimum>0.4</minimum>
+ * // Percentage increase of screen brightness needed to decrease the screen brightness
+ * // at a lux range above the specified threshold whilst in idle mode
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>19</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.13</threshold><percentage>20</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.23</threshold><percentage>21</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholdsIdle>
* </displayConfiguration>
* }
* </pre>
@@ -247,6 +359,13 @@
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+ private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
@@ -344,6 +463,31 @@
private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
private float mAmbientLuxDarkeningMinThreshold = 0.0f;
private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
+
+ // Screen brightness thresholds levels & percentages
+ private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+ private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+ // Screen brightness thresholds levels & percentages for idle mode
+ private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+ private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+ // Ambient brightness thresholds levels & percentages
+ private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+ private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+ // Ambient brightness thresholds levels & percentages for idle mode
+ private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+ private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
@@ -684,7 +828,7 @@
/**
* The minimum value for the ambient lux increase for a screen brightness change to actually
* occur.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxBrighteningMinThreshold() {
return mAmbientLuxBrighteningMinThreshold;
@@ -693,7 +837,7 @@
/**
* The minimum value for the ambient lux decrease for a screen brightness change to actually
* occur.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxDarkeningMinThreshold() {
return mAmbientLuxDarkeningMinThreshold;
@@ -702,7 +846,7 @@
/**
* The minimum value for the ambient lux increase for a screen brightness change to actually
* occur while in idle screen brightness mode.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxBrighteningMinThresholdIdle() {
return mAmbientLuxBrighteningMinThresholdIdle;
@@ -711,12 +855,262 @@
/**
* The minimum value for the ambient lux decrease for a screen brightness change to actually
* occur while in idle screen brightness mode.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxDarkeningMinThresholdIdle() {
return mAmbientLuxDarkeningMinThresholdIdle;
}
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenBrighteningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenBrighteningPercentages[n]
+ * level[MAX] <= value = mScreenBrighteningPercentages[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenBrighteningPercentages applies
+ */
+ public float[] getScreenBrighteningLevels() {
+ return mScreenBrighteningLevels;
+ }
+
+ /**
+ * The array that describes the screen brightening threshold percentage change at each screen
+ * brightness level described in mScreenBrighteningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change
+ */
+ public float[] getScreenBrighteningPercentages() {
+ return mScreenBrighteningPercentages;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenDarkeningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenDarkeningPercentages[n]
+ * level[MAX] <= value = mScreenDarkeningPercentages[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenDarkeningPercentages applies
+ */
+ public float[] getScreenDarkeningLevels() {
+ return mScreenDarkeningLevels;
+ }
+
+ /**
+ * The array that describes the screen darkening threshold percentage change at each screen
+ * brightness level described in mScreenDarkeningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change
+ */
+ public float[] getScreenDarkeningPercentages() {
+ return mScreenDarkeningPercentages;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold
+ * percentage applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientBrighteningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientBrighteningPercentages[n]
+ * level[MAX] <= value = mAmbientBrighteningPercentages[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientBrighteningPercentages applies
+ */
+ public float[] getAmbientBrighteningLevels() {
+ return mAmbientBrighteningLevels;
+ }
+
+ /**
+ * The array that describes the ambient brightening threshold percentage change at each ambient
+ * brightness level described in mAmbientBrighteningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change
+ */
+ public float[] getAmbientBrighteningPercentages() {
+ return mAmbientBrighteningPercentages;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientDarkeningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientDarkeningPercentages[n]
+ * level[MAX] <= value = mAmbientDarkeningPercentages[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientDarkeningPercentages applies
+ */
+ public float[] getAmbientDarkeningLevels() {
+ return mAmbientDarkeningLevels;
+ }
+
+ /**
+ * The array that describes the ambient darkening threshold percentage change at each ambient
+ * brightness level described in mAmbientDarkeningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change
+ */
+ public float[] getAmbientDarkeningPercentages() {
+ return mAmbientDarkeningPercentages;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenBrighteningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenBrighteningPercentagesIdle[n]
+ * level[MAX] <= value = mScreenBrighteningPercentagesIdle[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenBrighteningPercentagesIdle applies
+ */
+ public float[] getScreenBrighteningLevelsIdle() {
+ return mScreenBrighteningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the screen brightening threshold percentage change at each screen
+ * brightness level described in mScreenBrighteningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change while in idle mode.
+ */
+ public float[] getScreenBrighteningPercentagesIdle() {
+ return mScreenBrighteningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenDarkeningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenDarkeningPercentagesIdle[n]
+ * level[MAX] <= value = mScreenDarkeningPercentagesIdle[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenDarkeningPercentagesIdle applies
+ */
+ public float[] getScreenDarkeningLevelsIdle() {
+ return mScreenDarkeningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the screen darkening threshold percentage change at each screen
+ * brightness level described in mScreenDarkeningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change while in idle mode.
+ */
+ public float[] getScreenDarkeningPercentagesIdle() {
+ return mScreenDarkeningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientBrighteningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientBrighteningPercentagesIdle[n]
+ * level[MAX] <= value = mAmbientBrighteningPercentagesIdle[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientBrighteningPercentagesIdle applies
+ */
+ public float[] getAmbientBrighteningLevelsIdle() {
+ return mAmbientBrighteningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the ambient brightness threshold percentage change whilst in
+ * idle screen brightness mode at each ambient brightness level described in
+ * mAmbientBrighteningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of ambient brightness increase required in order
+ * for the screen brightness to change
+ */
+ public float[] getAmbientBrighteningPercentagesIdle() {
+ return mAmbientBrighteningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientDarkeningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientDarkeningPercentagesIdle[n]
+ * level[MAX] <= value = mAmbientDarkeningPercentagesIdle[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientDarkeningPercentagesIdle applies
+ */
+ public float[] getAmbientDarkeningLevelsIdle() {
+ return mAmbientDarkeningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the ambient brightness threshold percentage change whilst in
+ * idle screen brightness mode at each ambient brightness level described in
+ * mAmbientDarkeningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of ambient brightness decrease required in order
+ * for the screen brightness to change
+ */
+ public float[] getAmbientDarkeningPercentagesIdle() {
+ return mAmbientDarkeningPercentagesIdle;
+ }
+
SensorData getAmbientLightSensor() {
return mAmbientLightSensor;
}
@@ -812,14 +1206,17 @@
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ + "\n"
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
+ ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
+ ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis
+ ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis
+ + "\n"
+ ", mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ + "\n"
+ ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+ ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
+ ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
@@ -829,6 +1226,41 @@
+ ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
+ ", mAmbientLuxBrighteningMinThresholdIdle="
+ mAmbientLuxBrighteningMinThresholdIdle
+ + "\n"
+ + ", mScreenBrighteningLevels=" + Arrays.toString(
+ mScreenBrighteningLevels)
+ + ", mScreenBrighteningPercentages=" + Arrays.toString(
+ mScreenBrighteningPercentages)
+ + ", mScreenDarkeningLevels=" + Arrays.toString(
+ mScreenDarkeningLevels)
+ + ", mScreenDarkeningPercentages=" + Arrays.toString(
+ mScreenDarkeningPercentages)
+ + ", mAmbientBrighteningLevels=" + Arrays.toString(
+ mAmbientBrighteningLevels)
+ + ", mAmbientBrighteningPercentages=" + Arrays.toString(
+ mAmbientBrighteningPercentages)
+ + ", mAmbientDarkeningLevels=" + Arrays.toString(
+ mAmbientDarkeningLevels)
+ + ", mAmbientDarkeningPercentages=" + Arrays.toString(
+ mAmbientDarkeningPercentages)
+ + "\n"
+ + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString(
+ mAmbientBrighteningLevelsIdle)
+ + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
+ mAmbientBrighteningPercentagesIdle)
+ + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
+ mAmbientDarkeningLevelsIdle)
+ + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
+ mAmbientDarkeningPercentagesIdle)
+ + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
+ mScreenBrighteningLevelsIdle)
+ + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
+ mScreenBrighteningPercentagesIdle)
+ + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
+ mScreenDarkeningLevelsIdle)
+ + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
+ mScreenDarkeningPercentagesIdle)
+ + "\n"
+ ", mAmbientLightSensor=" + mAmbientLightSensor
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -914,6 +1346,7 @@
loadBrightnessMapFromConfigXml();
loadBrightnessRampsFromConfigXml();
loadAmbientLightSensorFromConfigXml();
+ loadBrightnessChangeThresholdsFromXml();
setProxSensorUnspecified();
loadAutoBrightnessConfigsFromConfigXml();
mLoadedFrom = "<config.xml>";
@@ -1454,91 +1887,287 @@
}
}
+ private void loadBrightnessChangeThresholdsFromXml() {
+ loadBrightnessChangeThresholds(/* config= */ null);
+ }
+
private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
- Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
- Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
- Thresholds displayBrightnessThresholdsIdle =
- config.getDisplayBrightnessChangeThresholdsIdle();
- Thresholds ambientBrightnessThresholdsIdle =
- config.getAmbientBrightnessChangeThresholdsIdle();
-
- loadDisplayBrightnessThresholds(displayBrightnessThresholds);
- loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
- loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
- loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+ loadDisplayBrightnessThresholds(config);
+ loadAmbientBrightnessThresholds(config);
+ loadDisplayBrightnessThresholdsIdle(config);
+ loadAmbientBrightnessThresholdsIdle(config);
}
- private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
- if (displayBrightnessThresholds != null) {
- BrightnessThresholds brighteningScreen =
- displayBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningScreen =
- displayBrightnessThresholds.getDarkeningThresholds();
+ private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
+ BrightnessThresholds brighteningScreen = null;
+ BrightnessThresholds darkeningScreen = null;
+ if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
+ brighteningScreen =
+ config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
+ darkeningScreen =
+ config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
- if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
- mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
- }
- if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
- mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
- }
+ }
+
+ // Screen bright/darkening threshold levels for active mode
+ Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningScreen,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+
+ mScreenBrighteningLevels = screenBrighteningPair.first;
+ mScreenBrighteningPercentages = screenBrighteningPair.second;
+
+ Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningScreen,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenDarkeningLevels = screenDarkeningPair.first;
+ mScreenDarkeningPercentages = screenDarkeningPair.second;
+
+ // Screen bright/darkening threshold minimums for active mode
+ if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+ mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
+ }
+ if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+ mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
}
}
- private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
- if (ambientBrightnessThresholds != null) {
- BrightnessThresholds brighteningAmbientLux =
- ambientBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningAmbientLux =
- ambientBrightnessThresholds.getDarkeningThresholds();
+ private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
+ // Ambient Brightness Threshold Levels
+ BrightnessThresholds brighteningAmbientLux = null;
+ BrightnessThresholds darkeningAmbientLux = null;
+ if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
+ brighteningAmbientLux =
+ config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
+ darkeningAmbientLux =
+ config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
+ }
- final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
- final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+ // Ambient bright/darkening threshold levels for active mode
+ Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningAmbientLux,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+ mAmbientBrighteningLevels = ambientBrighteningPair.first;
+ mAmbientBrighteningPercentages = ambientBrighteningPair.second;
- if (ambientBrighteningThreshold != null) {
- mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
- }
- if (ambientDarkeningThreshold != null) {
- mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
- }
+ Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningAmbientLux,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+ mAmbientDarkeningLevels = ambientDarkeningPair.first;
+ mAmbientDarkeningPercentages = ambientDarkeningPair.second;
+
+ // Ambient bright/darkening threshold minimums for active/idle mode
+ if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThreshold =
+ brighteningAmbientLux.getMinimum().floatValue();
+ }
+
+ if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
}
}
- private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
- if (idleDisplayBrightnessThresholds != null) {
- BrightnessThresholds brighteningScreenIdle =
- idleDisplayBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningScreenIdle =
- idleDisplayBrightnessThresholds.getDarkeningThresholds();
+ private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
+ BrightnessThresholds brighteningScreenIdle = null;
+ BrightnessThresholds darkeningScreenIdle = null;
+ if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
+ brighteningScreenIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+ darkeningScreenIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+ }
- if (brighteningScreenIdle != null
- && brighteningScreenIdle.getMinimum() != null) {
- mScreenBrighteningMinThresholdIdle =
- brighteningScreenIdle.getMinimum().floatValue();
- }
- if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
- mScreenDarkeningMinThresholdIdle =
- darkeningScreenIdle.getMinimum().floatValue();
- }
+ Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningScreenIdle,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
+ mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
+
+ Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningScreenIdle,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
+ mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
+
+ if (brighteningScreenIdle != null
+ && brighteningScreenIdle.getMinimum() != null) {
+ mScreenBrighteningMinThresholdIdle =
+ brighteningScreenIdle.getMinimum().floatValue();
+ }
+ if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+ mScreenDarkeningMinThresholdIdle =
+ darkeningScreenIdle.getMinimum().floatValue();
}
}
- private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
- if (idleAmbientBrightnessThresholds != null) {
- BrightnessThresholds brighteningAmbientLuxIdle =
- idleAmbientBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningAmbientLuxIdle =
- idleAmbientBrightnessThresholds.getDarkeningThresholds();
+ private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
+ BrightnessThresholds brighteningAmbientLuxIdle = null;
+ BrightnessThresholds darkeningAmbientLuxIdle = null;
+ if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
+ brighteningAmbientLuxIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+ darkeningAmbientLuxIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+ }
- if (brighteningAmbientLuxIdle != null
- && brighteningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxBrighteningMinThresholdIdle =
- brighteningAmbientLuxIdle.getMinimum().floatValue();
+ Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningAmbientLuxIdle,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+ mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
+ mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
+
+ Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningAmbientLuxIdle,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+ mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
+ mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
+
+ if (brighteningAmbientLuxIdle != null
+ && brighteningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThresholdIdle =
+ brighteningAmbientLuxIdle.getMinimum().floatValue();
+ }
+
+ if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThresholdIdle =
+ darkeningAmbientLuxIdle.getMinimum().floatValue();
+ }
+ }
+
+ private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
+ float[] defaultPercentage) {
+ return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
+ configFallbackPercentage, defaultLevels, defaultPercentage, false);
+ }
+
+ // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+ // percentages for brightness levels at or above the lux value.
+ // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+ // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+ // rest of the framework. Values were also defined in different units (permille vs percent).
+ private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPermille,
+ float[] defaultLevels, float[] defaultPercentage,
+ boolean potentialOldBrightnessScale) {
+ if (thresholds != null
+ && thresholds.getBrightnessThresholdPoints() != null
+ && thresholds.getBrightnessThresholdPoints()
+ .getBrightnessThresholdPoint().size() != 0) {
+
+ // The level and percentages arrays are equal length in the ddc (new system)
+ List<ThresholdPoint> points =
+ thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+ final int size = points.size();
+
+ float[] thresholdLevels = new float[size];
+ float[] thresholdPercentages = new float[size];
+
+ int i = 0;
+ for (ThresholdPoint point : points) {
+ thresholdLevels[i] = point.getThreshold().floatValue();
+ thresholdPercentages[i] = point.getPercentage().floatValue();
+ i++;
}
- if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxDarkeningMinThresholdIdle =
- darkeningAmbientLuxIdle.getMinimum().floatValue();
+ return new Pair<>(thresholdLevels, thresholdPercentages);
+ } else {
+ // The level and percentages arrays are unequal length in config.xml (old system)
+ // We prefix the array with a 0 value to ensure they can be handled consistently
+ // with the new system.
+
+ // Load levels array
+ int[] configThresholdArray = mContext.getResources().getIntArray(
+ configFallbackThreshold);
+ int configThresholdsSize;
+ if (configThresholdArray == null || configThresholdArray.length == 0) {
+ configThresholdsSize = 1;
+ } else {
+ configThresholdsSize = configThresholdArray.length + 1;
+ }
+
+
+ // Load percentage array
+ int[] configPermille = mContext.getResources().getIntArray(
+ configFallbackPermille);
+
+ // Ensure lengths match up
+ boolean emptyArray = configPermille == null || configPermille.length == 0;
+ if (emptyArray && configThresholdsSize == 1) {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ if (emptyArray || configPermille.length != configThresholdsSize) {
+ throw new IllegalArgumentException(
+ "Brightness threshold arrays do not align in length");
+ }
+
+ // Calculate levels array
+ float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+ // Start at 1, so that 0 index value is 0.0f (default)
+ for (int i = 1; i < configThresholdsSize; i++) {
+ configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+ }
+ if (potentialOldBrightnessScale) {
+ configThresholdWithZeroPrefixed =
+ constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+ }
+
+ // Calculate percentages array
+ float[] configPercentage = new float[configThresholdsSize];
+ for (int i = 0; i < configPermille.length; i++) {
+ configPercentage[i] = configPermille[i] / 10.0f;
+ }
+ return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+ }
+ }
+
+ /**
+ * This check is due to historical reasons, where screen thresholdLevels used to be
+ * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+ * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+ * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+ */
+ private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+ if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+ /* maxValueInclusive= */ 1.0f)) {
+ return thresholdLevels;
+ }
+
+ Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+ float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+ for (int index = 0; thresholdLevels.length > index; ++index) {
+ thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+ }
+ return thresholdLevelsScaled;
+ }
+
+ private boolean isAllInRange(float[] configArray, float minValueInclusive,
+ float maxValueInclusive) {
+ for (float v : configArray) {
+ if (v < minValueInclusive || v > maxValueInclusive) {
+ return false;
}
}
+ return true;
}
private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index efd2e34..458cf1a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -956,53 +956,77 @@
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
- int[] ambientBrighteningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_ambientBrighteningThresholds);
- int[] ambientDarkeningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_ambientDarkeningThresholds);
- int[] ambientThresholdLevels = resources.getIntArray(
- com.android.internal.R.array.config_ambientThresholdLevels);
+ // Ambient Lux - Active Mode Brightness Thresholds
+ float[] ambientBrighteningThresholds =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+ float[] ambientDarkeningThresholds =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+ float[] ambientBrighteningLevels =
+ mDisplayDeviceConfig.getAmbientBrighteningLevels();
+ float[] ambientDarkeningLevels =
+ mDisplayDeviceConfig.getAmbientDarkeningLevels();
float ambientDarkeningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
- int[] screenBrighteningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_screenBrighteningThresholds);
- int[] screenDarkeningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_screenDarkeningThresholds);
- float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
- .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
+ // Display - Active Mode Brightness Thresholds
+ float[] screenBrighteningThresholds =
+ mDisplayDeviceConfig.getScreenBrighteningPercentages();
+ float[] screenDarkeningThresholds =
+ mDisplayDeviceConfig.getScreenDarkeningPercentages();
+ float[] screenBrighteningLevels =
+ mDisplayDeviceConfig.getScreenBrighteningLevels();
+ float[] screenDarkeningLevels =
+ mDisplayDeviceConfig.getScreenDarkeningLevels();
float screenDarkeningMinThreshold =
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
- screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+ screenBrighteningThresholds, screenDarkeningThresholds,
+ screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+ screenBrighteningMinThreshold);
- // Idle screen thresholds
- float screenDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
- float screenBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
- screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
- // Idle ambient thresholds
+ // Ambient Lux - Idle Screen Brightness Thresholds
float ambientDarkeningMinThresholdIdle =
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
float ambientBrighteningMinThresholdIdle =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ float[] ambientBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+ float[] ambientDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+ float[] ambientBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+ float[] ambientDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
- ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
- ambientBrighteningMinThresholdIdle);
+ ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+ ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+ ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+
+ // Display - Idle Screen Brightness Thresholds
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ float[] screenBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+ float[] screenDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+ float[] screenBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+ float[] screenDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+ screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
long brighteningLightDebounce = mDisplayDeviceConfig
.getAutoBrightnessBrighteningLightDebounce();
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index abf8fe3..faa4c3d 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -29,52 +29,38 @@
private static final boolean DEBUG = false;
- private final float[] mBrighteningThresholds;
- private final float[] mDarkeningThresholds;
- private final float[] mThresholdLevels;
+ private final float[] mBrighteningThresholdsPercentages;
+ private final float[] mDarkeningThresholdsPercentages;
+ private final float[] mBrighteningThresholdLevels;
+ private final float[] mDarkeningThresholdLevels;
private final float mMinDarkening;
private final float mMinBrightening;
/**
- * Creates a {@code HysteresisLevels} object for ambient brightness.
- * @param brighteningThresholds an array of brightening hysteresis constraint constants.
- * @param darkeningThresholds an array of darkening hysteresis constraint constants.
- * @param thresholdLevels a monotonically increasing array of threshold levels.
- * @param minBrighteningThreshold the minimum value for which the brightening value needs to
- * return.
- * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
- */
- HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
- if (brighteningThresholds.length != darkeningThresholds.length
- || darkeningThresholds.length != thresholdLevels.length + 1) {
- throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
- }
- mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
- mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
- mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
- mMinDarkening = minDarkeningThreshold;
- mMinBrightening = minBrighteningThreshold;
- }
-
- /**
- * Creates a {@code HysteresisLevels} object for screen brightness.
- * @param brighteningThresholds an array of brightening hysteresis constraint constants.
- * @param darkeningThresholds an array of darkening hysteresis constraint constants.
- * @param thresholdLevels a monotonically increasing array of threshold levels.
+ * Creates a {@code HysteresisLevels} object with the given equal-length
+ * float arrays.
+ * @param brighteningThresholdsPercentages 0-100 of thresholds
+ * @param darkeningThresholdsPercentages 0-100 of thresholds
+ * @param brighteningThresholdLevels float array of brightness values in the relevant units
+ * @param darkeningThresholdLevels float array of brightness values in the relevant units
* @param minBrighteningThreshold the minimum value for which the brightening value needs to
* return.
* @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
*/
- HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
- if (brighteningThresholds.length != darkeningThresholds.length
- || darkeningThresholds.length != thresholdLevels.length + 1) {
+ HysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages,
+ float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+ float minDarkeningThreshold, float minBrighteningThreshold) {
+ if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+ || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
}
- mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
- mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
- mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels);
+ mBrighteningThresholdsPercentages =
+ setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+ mDarkeningThresholdsPercentages =
+ setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+ mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+ mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
mMinDarkening = minDarkeningThreshold;
mMinBrightening = minBrighteningThreshold;
}
@@ -83,7 +69,9 @@
* Return the brightening hysteresis threshold for the given value level.
*/
public float getBrighteningThreshold(float value) {
- final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+ final float brightConstant = getReferenceLevel(value,
+ mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
@@ -98,7 +86,8 @@
* Return the darkening hysteresis threshold for the given value level.
*/
public float getDarkeningThreshold(float value) {
- final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+ final float darkConstant = getReferenceLevel(value,
+ mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
@@ -111,60 +100,39 @@
/**
* Return the hysteresis constant for the closest threshold value from the given array.
*/
- private float getReferenceLevel(float value, float[] referenceLevels) {
- int index = 0;
- while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
- ++index;
+ private float getReferenceLevel(float value, float[] thresholdLevels,
+ float[] thresholdPercentages) {
+ if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+ return 0.0f;
}
- return referenceLevels[index];
+ int index = 0;
+ while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+ index++;
+ }
+ return thresholdPercentages[index];
}
/**
* Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
*/
- private float[] setArrayFormat(int[] configArray, float divideFactor) {
+ private float[] setArrayFormat(float[] configArray, float divideFactor) {
float[] levelArray = new float[configArray.length];
for (int index = 0; levelArray.length > index; ++index) {
- levelArray[index] = (float) configArray[index] / divideFactor;
+ levelArray[index] = configArray[index] / divideFactor;
}
return levelArray;
}
- /**
- * This check is due to historical reasons, where screen thresholdLevels used to be
- * integer values in the range of [0-255], but then was changed to be float values from [0,1].
- * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0,
- * 1], and if not, we divide all the levels with 255 to bring them down to the same scale.
- */
- private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
- if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */
- 1.0f)) {
- return thresholdLevels;
- }
-
- Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
- float[] thresholdLevelsScaled = new float[thresholdLevels.length];
- for (int index = 0; thresholdLevels.length > index; ++index) {
- thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
- }
- return thresholdLevelsScaled;
- }
-
- private boolean isAllInRange(float[] configArray, float minValueInclusive,
- float maxValueInclusive) {
- int configArraySize = configArray.length;
- for (int index = 0; configArraySize > index; ++index) {
- if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) {
- return false;
- }
- }
- return true;
- }
void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
- pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
- pw.println(" mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
- pw.println(" mThresholdLevels=" + Arrays.toString(mThresholdLevels));
+ pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
+ pw.println(" mBrighteningThresholdsPercentages="
+ + Arrays.toString(mBrighteningThresholdsPercentages));
+ pw.println(" mMinBrightening=" + mMinBrightening);
+ pw.println(" mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
+ pw.println(" mDarkeningThresholdsPercentages="
+ + Arrays.toString(mDarkeningThresholdsPercentages));
+ pw.println(" mMinDarkening=" + mMinDarkening);
}
}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
new file mode 100644
index 0000000..c57d7e7
--- /dev/null
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.input.IInputDeviceBatteryListener;
+import android.hardware.input.InputManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the battery state
+ * of input devices.
+ */
+final class BatteryController {
+ private static final String TAG = BatteryController.class.getSimpleName();
+
+ // To enable these logs, run:
+ // 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final NativeInputManagerService mNative;
+
+ // Maps a pid to the registered listener record for that process. There can only be one battery
+ // listener per process.
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
+
+ BatteryController(Context context, NativeInputManagerService nativeService) {
+ mContext = context;
+ mNative = nativeService;
+ }
+
+ /**
+ * Register the battery listener for the given input device and start monitoring its battery
+ * state.
+ */
+ @BinderThread
+ void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+ int pid) {
+ synchronized (mLock) {
+ ListenerRecord listenerRecord = mListenerRecords.get(pid);
+
+ if (listenerRecord == null) {
+ listenerRecord = new ListenerRecord(pid, listener);
+ try {
+ listener.asBinder().linkToDeath(listenerRecord.mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Client died before battery listener could be registered.");
+ return;
+ }
+ mListenerRecords.put(pid, listenerRecord);
+ if (DEBUG) Slog.d(TAG, "Battery listener added for pid " + pid);
+ }
+
+ if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
+ throw new SecurityException(
+ "Cannot register a new battery listener when there is already another "
+ + "registered listener for pid "
+ + pid);
+ }
+ if (!listenerRecord.mMonitoredDevices.add(deviceId)) {
+ throw new IllegalArgumentException(
+ "The battery listener for pid " + pid
+ + " is already monitoring deviceId " + deviceId);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Battery listener for pid " + pid
+ + " is monitoring deviceId " + deviceId);
+ }
+
+ notifyBatteryListener(deviceId, listenerRecord);
+ }
+ }
+
+ private void notifyBatteryListener(int deviceId, ListenerRecord record) {
+ final long eventTime = SystemClock.uptimeMillis();
+ try {
+ record.mListener.onBatteryStateChanged(
+ deviceId,
+ hasBattery(deviceId),
+ mNative.getBatteryStatus(deviceId),
+ mNative.getBatteryCapacity(deviceId) / 100.f,
+ eventTime);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify listener", e);
+ }
+ }
+
+ private boolean hasBattery(int deviceId) {
+ final InputDevice device =
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .getInputDevice(deviceId);
+ return device != null && device.hasBattery();
+ }
+
+ /**
+ * Unregister the battery listener for the given input device and stop monitoring its battery
+ * state. If there are no other input devices that this listener is monitoring, the listener is
+ * removed.
+ */
+ @BinderThread
+ void unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+ int pid) {
+ synchronized (mLock) {
+ final ListenerRecord listenerRecord = mListenerRecords.get(pid);
+ if (listenerRecord == null) {
+ throw new IllegalArgumentException(
+ "Cannot unregister battery callback: No listener registered for pid "
+ + pid);
+ }
+
+ if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
+ throw new IllegalArgumentException(
+ "Cannot unregister battery callback: The listener is not the one that "
+ + "is registered for pid "
+ + pid);
+ }
+
+ if (!listenerRecord.mMonitoredDevices.contains(deviceId)) {
+ throw new IllegalArgumentException(
+ "Cannot unregister battery callback: The device is not being "
+ + "monitored for deviceId " + deviceId);
+ }
+
+ unregisterRecordLocked(listenerRecord, deviceId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unregisterRecordLocked(ListenerRecord listenerRecord, int deviceId) {
+ final int pid = listenerRecord.mPid;
+
+ if (!listenerRecord.mMonitoredDevices.remove(deviceId)) {
+ throw new IllegalStateException("Cannot unregister battery callback: The deviceId "
+ + deviceId
+ + " is not being monitored by pid "
+ + pid);
+ }
+
+ if (listenerRecord.mMonitoredDevices.isEmpty()) {
+ // There are no more devices being monitored by this listener.
+ listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0);
+ mListenerRecords.remove(pid);
+ if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid);
+ }
+ }
+
+ private void handleListeningProcessDied(int pid) {
+ synchronized (mLock) {
+ final ListenerRecord listenerRecord = mListenerRecords.get(pid);
+ if (listenerRecord == null) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Removing battery listener for pid " + pid + " because the process died");
+ }
+ for (final int deviceId : listenerRecord.mMonitoredDevices) {
+ unregisterRecordLocked(listenerRecord, deviceId);
+ }
+ }
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + TAG + ": " + mListenerRecords.size()
+ + " battery listeners");
+ for (int i = 0; i < mListenerRecords.size(); i++) {
+ pw.println(prefix + " " + i + ": " + mListenerRecords.valueAt(i));
+ }
+ }
+ }
+
+ @SuppressWarnings("all")
+ void monitor() {
+ synchronized (mLock) {
+ return;
+ }
+ }
+
+ // A record of a registered battery listener from one process.
+ private class ListenerRecord {
+ final int mPid;
+ final IInputDeviceBatteryListener mListener;
+ final IBinder.DeathRecipient mDeathRecipient;
+ // The set of deviceIds that are currently being monitored by this listener.
+ final Set<Integer> mMonitoredDevices;
+
+ ListenerRecord(int pid, IInputDeviceBatteryListener listener) {
+ mPid = pid;
+ mListener = listener;
+ mMonitoredDevices = new ArraySet<>();
+ mDeathRecipient = () -> handleListeningProcessDied(pid);
+ }
+
+ @Override
+ public String toString() {
+ return "pid=" + mPid
+ + ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 72612a0..91d5698 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -47,6 +47,7 @@
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayViewport;
+import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.IInputSensorEventListener;
@@ -318,6 +319,9 @@
@GuardedBy("mInputMonitors")
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+ // Manages battery state for input devices.
+ private final BatteryController mBatteryController;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -425,6 +429,7 @@
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
+ mBatteryController = new BatteryController(mContext, mNative);
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -2674,6 +2679,18 @@
}
@Override
+ public void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener);
+ mBatteryController.registerBatteryListener(deviceId, listener, Binder.getCallingPid());
+ }
+
+ @Override
+ public void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener);
+ mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -2686,7 +2703,8 @@
pw.println("Input Manager Service (Java) State:");
dumpAssociations(pw, " " /*prefix*/);
dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
- dumpDisplayInputPropertiesValues(pw, " " /* prefix */);
+ dumpDisplayInputPropertiesValues(pw, " " /*prefix*/);
+ mBatteryController.dump(pw, " " /*prefix*/);
}
private void dumpAssociations(PrintWriter pw, String prefix) {
@@ -2797,6 +2815,7 @@
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
+ mBatteryController.monitor();
mNative.monitor();
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 5a2fb18..dad9584 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,7 +571,8 @@
/**
* Called when there has been user activity.
*/
- public void onUserActivity(int displayGroupId, int event, int uid) {
+ public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
+ int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index fec61ac..9fe53fb 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,6 +74,8 @@
private long mLastPowerOnTime;
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
+ @PowerManager.UserActivityEvent
+ private int mLastUserActivityEvent;
/** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -244,7 +246,7 @@
return true;
}
- boolean dozeLocked(long eventTime, int uid, int reason) {
+ boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
return false;
}
@@ -253,9 +255,14 @@
try {
reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+ long millisSinceLastUserActivity = eventTime - Math.max(
+ mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
Slog.i(TAG, "Powering off display group due to "
- + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId()
- + ", uid= " + uid + ")...");
+ + PowerManager.sleepReasonToString(reason)
+ + " (groupId= " + getGroupId() + ", uid= " + uid
+ + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
+ + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
+ mLastUserActivityEvent) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -266,14 +273,16 @@
return true;
}
- boolean sleepLocked(long eventTime, int uid, int reason) {
+ boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
return false;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
try {
- Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+ Slog.i(TAG,
+ "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
+ + PowerManager.sleepReasonToString(reason) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
/* opPackageName= */ null, /* details= */ null);
@@ -287,16 +296,20 @@
return mLastUserActivityTime;
}
- void setLastUserActivityTimeLocked(long lastUserActivityTime) {
+ void setLastUserActivityTimeLocked(long lastUserActivityTime,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTime = lastUserActivityTime;
+ mLastUserActivityEvent = event;
}
public long getLastUserActivityTimeNoChangeLightsLocked() {
return mLastUserActivityTimeNoChangeLights;
}
- public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
+ public void setLastUserActivityTimeNoChangeLightsLocked(long time,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTimeNoChangeLights = time;
+ mLastUserActivityEvent = event;
}
public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dbf05f1..3a05fc3 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1169,6 +1169,7 @@
return;
}
+ Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1888,12 +1889,13 @@
// Called from native code.
@SuppressWarnings("unused")
- private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
+ private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
+ int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(int displayId, long eventTime, int event, int flags,
- int uid) {
+ private void userActivityInternal(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
synchronized (mLock) {
if (displayId == Display.INVALID_DISPLAY) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1944,11 +1946,12 @@
@GuardedBy("mLock")
private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
- int event, int flags, int uid) {
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
final int groupId = powerGroup.getGroupId();
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
- + ", eventTime=" + eventTime + ", event=" + event
+ + ", eventTime=" + eventTime
+ + ", event=" + PowerManager.userActivityEventToString(event)
+ ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
@@ -1983,7 +1986,7 @@
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
&& eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
+ powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -1993,7 +1996,7 @@
}
} else {
if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeLocked(eventTime);
+ powerGroup.setLastUserActivityTimeLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2020,7 +2023,8 @@
@WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
if (mForceSuspendActive || !mSystemReady) {
return;
@@ -2043,11 +2047,11 @@
@GuardedBy("mLock")
private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- int reason, int uid) {
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
- + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mSystemReady || !mBootCompleted) {
@@ -2058,10 +2062,12 @@
}
@GuardedBy("mLock")
- private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
- int uid) {
+ private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
+ Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mBootCompleted || !mSystemReady) {
return false;
@@ -2122,8 +2128,10 @@
case WAKEFULNESS_DOZING:
traceMethodName = "goToSleep";
Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
- + " (uid " + uid + ")...");
-
+ + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
+ + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
+ + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
+ + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
mLastGlobalSleepTime = eventTime;
mLastGlobalSleepReason = reason;
mDozeStartInProgress = true;
@@ -4207,7 +4215,7 @@
void onUserActivity() {
synchronized (mLock) {
mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
- mClock.uptimeMillis());
+ mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
}
}
@@ -5590,7 +5598,8 @@
}
@Override // Binder call
- public void userActivity(int displayId, long eventTime, int event, int flags) {
+ public void userActivity(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags) {
final long now = mClock.uptimeMillis();
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f888ff6..2888b9a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -602,9 +602,12 @@
synchronized (mUserTrustState) {
wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+ boolean isAutomotive = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
boolean renewingTrust = wasTrustable && (
(flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
- boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+ boolean canMoveToTrusted =
+ alreadyUnlocked || isFromUnlock || renewingTrust || isAutomotive;
boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
if (trustedByAtLeastOneAgent && wasTrusted) {
@@ -687,7 +690,7 @@
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -2084,7 +2087,7 @@
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d2a00af..eca2e74 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -27,6 +27,8 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
@@ -332,8 +334,8 @@
}
@Override
- public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
- Intent resultData) {
+ public boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType,
+ int resultCode, Intent resultData) {
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
@@ -348,7 +350,7 @@
synchronized (mGlobalLock) {
return r.getRootTask().navigateUpTo(
- r, destIntent, destGrants, resultCode, resultData, resultGrants);
+ r, destIntent, resolvedType, destGrants, resultCode, resultData, resultGrants);
}
}
@@ -707,7 +709,26 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- return r != null && r.setOccludesParent(true);
+ // Create a transition if the activity is playing in case the below activity didn't
+ // commit invisible. That's because if any activity below this one has changed its
+ // visibility while playing transition, there won't able to commit visibility until
+ // the running transition finish.
+ final Transition transition = r != null
+ && r.mTransitionController.inPlayingTransition(r)
+ ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
+ if (transition != null) {
+ r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ final boolean changed = r != null && r.setOccludesParent(true);
+ if (transition != null) {
+ if (changed) {
+ r.mTransitionController.setReady(r.getDisplayContent());
+ } else {
+ transition.abort();
+ }
+ }
+ return changed;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -728,7 +749,25 @@
if (under != null) {
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
- return r.setOccludesParent(false);
+ // Create a transition if the activity is playing in case the current activity
+ // didn't commit invisible. That's because if this activity has changed its
+ // visibility while playing transition, there won't able to commit visibility until
+ // the running transition finish.
+ final Transition transition = r.mTransitionController.inPlayingTransition(r)
+ ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+ if (transition != null) {
+ r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ final boolean changed = r.setOccludesParent(false);
+ if (transition != null) {
+ if (changed) {
+ r.mTransitionController.setReady(r.getDisplayContent());
+ } else {
+ transition.abort();
+ }
+ }
+ return changed;
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8486e7..9c080e8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -120,6 +120,8 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
@@ -658,7 +660,7 @@
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
- boolean mUseTransferredAnimation;
+ int mTransitionChangeFlags;
/** Whether we need to setup the animation to animate only within the letterbox. */
private boolean mNeedsLetterboxedAnimation;
@@ -4395,10 +4397,10 @@
// When transferring an animation, we no longer need to apply an animation to
// the token we transfer the animation over. Thus, set this flag to indicate
// we've transferred the animation.
- mUseTransferredAnimation = true;
+ mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
} else if (mTransitionController.getTransitionPlayer() != null) {
// In the new transit system, just set this every time we transfer the window
- mUseTransferredAnimation = true;
+ mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
}
// Post cleanup after the visibility and animation are transferred.
fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -5247,6 +5249,10 @@
// If in a transition, defer commits for activities that are going invisible
if (!visible && inTransition()) {
+ if (mTransitionController.inPlayingTransition(this)
+ && mTransitionController.isCollecting(this)) {
+ mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ }
return;
}
// If we are preparing an app transition, then delay changing
@@ -5297,7 +5303,7 @@
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
- if (mUseTransferredAnimation) {
+ if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return false;
}
// If it was set to true, reset the last request to force the transition.
@@ -5370,7 +5376,7 @@
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
- mUseTransferredAnimation = false;
+ mTransitionChangeFlags = 0;
postApplyAnimation(visible, fromTransition);
}
@@ -5812,8 +5818,8 @@
// in untrusted mode. Traverse bottom to top with boundary so that it will only check
// activities above this activity.
final ActivityRecord differentUidOverlayActivity = getTask().getActivity(
- a -> a.getUid() != getUid(), this /* boundary */, false /* includeBoundary */,
- false /* traverseTopToBottom */);
+ a -> !a.finishing && a.getUid() != getUid(), this /* boundary */,
+ false /* includeBoundary */, false /* traverseTopToBottom */);
return differentUidOverlayActivity != null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 0707b81..30454d4 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,6 +50,7 @@
import android.util.SparseArray;
import android.view.RemoteAnimationAdapter;
import android.view.WindowManager;
+import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -563,14 +564,39 @@
return false;
}
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
- final ActivityMetricsLogger.LaunchingState launchingState =
- mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
- final Task task = r.getTask();
- mService.deferWindowLayout();
- try {
+ final RemoteTransition remote = options.getRemoteTransition();
+ if (remote != null && rootTask.mTransitionController.isCollecting()) {
+ final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, rootTask.mTransitionController,
+ mService.mWindowManager.mSyncEngine);
+ // Special case: we are entering recents while an existing transition is running. In
+ // this case, we know it's safe to "defer" the activity launch, so lets do so now so
+ // that it can get its own transition and thus update launcher correctly.
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ () -> rootTask.mTransitionController.moveToCollecting(transition),
+ () -> {
+ final Task task = r.getTask();
+ task.mTransitionController.requestStartTransition(transition,
+ task, remote, null /* displayChange */);
+ task.mTransitionController.collect(task);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ });
+ } else {
+ final Task task = r.getTask();
task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
0 /* flags */, task, task /* readyGroupRef */,
options.getRemoteTransition(), null /* displayChange */);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ }
+ return true;
+ }
+
+ void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
+ ActivityRecord r, Task task, Task rootTask) {
+ final ActivityMetricsLogger.LaunchingState launchingState =
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+ mService.deferWindowLayout();
+ try {
r.mTransitionController.setTransientLaunch(r,
TaskDisplayArea.getRootTaskAbove(rootTask));
task.moveToFront("startExistingRecents");
@@ -582,7 +608,6 @@
task.mInResumeTopActivity = false;
mService.continueWindowLayout();
}
- return true;
}
void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index e80c260..0bfc48b 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -98,7 +98,7 @@
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
return mService.getRecentTasks().createRecentTaskInfo(task,
- false /* stripExtras */);
+ false /* stripExtras */, true /* getTasksAllowed */);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 317c93e..ea82417 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -481,7 +481,7 @@
}
private void updateRoundedCorners(WindowState mainWindow) {
- final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
+ final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
if (windowSurface != null && windowSurface.isValid()) {
final Transaction transaction = mActivityRecord.getSyncTransaction();
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4860762..1fc061b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
continue;
}
- res.add(createRecentTaskInfo(task, true /* stripExtras */));
+ res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
}
return res;
}
@@ -1895,7 +1895,8 @@
/**
* Creates a new RecentTaskInfo from a Task.
*/
- ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+ boolean getTasksAllowed) {
final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
// If the recent Task is detached, we consider it will be re-attached to the default
// TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1907,6 +1908,9 @@
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+ if (!getTasksAllowed) {
+ Task.trimIneffectiveInfo(tr, rti);
+ }
// Fill in organized child task info for the task created by organizer.
if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 120fec0..0e60274 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -142,6 +142,10 @@
task.fillTaskInfo(rti, !mKeepIntentExtra);
// Fill in some deprecated values
rti.id = rti.taskId;
+
+ if (!mAllowed) {
+ Task.trimIneffectiveInfo(task, rti);
+ }
return rti;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index aede32c..9f45f02 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3446,6 +3446,27 @@
info.isSleeping = shouldSleepActivities();
}
+ /**
+ * Removes the activity info if the activity belongs to a different uid, which is
+ * different from the app that hosts the task.
+ */
+ static void trimIneffectiveInfo(Task task, TaskInfo info) {
+ final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+ false /* traverseTopToBottom */);
+ final int baseActivityUid =
+ baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+ if (info.topActivityInfo != null
+ && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+ info.topActivity = null;
+ info.topActivityInfo = null;
+ }
+
+ if (task.effectiveUid != baseActivityUid) {
+ info.baseActivity = null;
+ }
+ }
+
@Nullable PictureInPictureParams getPictureInPictureParams() {
final Task topTask = getTopMostTask();
if (topTask == null) return null;
@@ -3525,12 +3546,16 @@
* {@link android.window.TaskFragmentOrganizer}
*/
TaskFragmentParentInfo getTaskFragmentParentInfo() {
- return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested());
+ return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
+ shouldBeVisible(null /* starting */));
}
@Override
void onActivityVisibleRequestedChanged() {
- if (mVisibleRequested != isVisibleRequested()) {
+ final boolean prevVisibleRequested = mVisibleRequested;
+ // mVisibleRequested is updated in super method.
+ super.onActivityVisibleRequestedChanged();
+ if (prevVisibleRequested != mVisibleRequested) {
sendTaskFragmentParentInfoChangedIfNeeded();
}
}
@@ -5308,8 +5333,9 @@
return false;
}
- boolean navigateUpTo(ActivityRecord srec, Intent destIntent, NeededUriGrants destGrants,
- int resultCode, Intent resultData, NeededUriGrants resultGrants) {
+ boolean navigateUpTo(ActivityRecord srec, Intent destIntent, String resolvedType,
+ NeededUriGrants destGrants, int resultCode, Intent resultData,
+ NeededUriGrants resultGrants) {
if (!srec.attachedToProcess()) {
// Nothing to do if the caller is not attached, because this method should be called
// from an alive activity.
@@ -5402,28 +5428,22 @@
srec.packageName);
}
} else {
- try {
- ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
- destIntent.getComponent(), ActivityManagerService.STOCK_PM_FLAGS,
- srec.mUserId);
- // TODO(b/64750076): Check if calling pid should really be -1.
- final int res = mAtmService.getActivityStartController()
- .obtainStarter(destIntent, "navigateUpTo")
- .setCaller(srec.app.getThread())
- .setActivityInfo(aInfo)
- .setResultTo(parent.token)
- .setCallingPid(-1)
- .setCallingUid(callingUid)
- .setCallingPackage(srec.packageName)
- .setCallingFeatureId(parent.launchedFromFeatureId)
- .setRealCallingPid(-1)
- .setRealCallingUid(callingUid)
- .setComponentSpecified(true)
- .execute();
- foundParentInTask = res == ActivityManager.START_SUCCESS;
- } catch (RemoteException e) {
- foundParentInTask = false;
- }
+ // TODO(b/64750076): Check if calling pid should really be -1.
+ final int res = mAtmService.getActivityStartController()
+ .obtainStarter(destIntent, "navigateUpTo")
+ .setResolvedType(resolvedType)
+ .setUserId(srec.mUserId)
+ .setCaller(srec.app.getThread())
+ .setResultTo(parent.token)
+ .setCallingPid(-1)
+ .setCallingUid(callingUid)
+ .setCallingPackage(srec.packageName)
+ .setCallingFeatureId(parent.launchedFromFeatureId)
+ .setRealCallingPid(-1)
+ .setRealCallingUid(callingUid)
+ .setComponentSpecified(true)
+ .execute();
+ foundParentInTask = res == ActivityManager.START_SUCCESS;
parent.finishIfPossible(resultCode, resultData, resultGrants,
"navigate-top", true /* oomAdj */);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 058a066..d178676 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2690,12 +2690,26 @@
return;
}
mVisibleRequested = isVisibleRequested;
- final TaskFragment parentTf = getParent().asTaskFragment();
+ final WindowContainer<?> parent = getParent();
+ if (parent == null) {
+ return;
+ }
+ final TaskFragment parentTf = parent.asTaskFragment();
if (parentTf != null) {
parentTf.onActivityVisibleRequestedChanged();
}
}
+ @Nullable
+ @Override
+ TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
+ final TaskFragment taskFragment = super.getTaskFragment(callback);
+ if (taskFragment != null) {
+ return taskFragment;
+ }
+ return callback.test(this) ? this : null;
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 2d5c989..867833a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -920,6 +920,7 @@
for (int i = 0, n = pendingEvents.size(); i < n; i++) {
final PendingTaskFragmentEvent event = pendingEvents.get(i);
final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
+ // TODO(b/251132298): move visibility check to the client side.
if (task != null && (task.lastActiveTime <= event.mDeferTime
|| !(isTaskVisible(task, visibleTasks, invisibleTasks)
|| shouldSendEventWhenTaskInvisible(event)))) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 147c9cb..526a366 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -55,7 +55,6 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
@@ -1331,7 +1330,7 @@
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" sibling is a participant with mode %s",
TransitionInfo.modeToString(siblingMode));
- if (mode != siblingMode) {
+ if (reduceMode(mode) != reduceMode(siblingMode)) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" SKIP: common mode mismatch. was %s",
TransitionInfo.modeToString(mode));
@@ -1341,6 +1340,16 @@
return true;
}
+ /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
+ @TransitionInfo.TransitionMode
+ private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
+ switch (mode) {
+ case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
+ case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
+ default: return mode;
+ }
+ }
+
/**
* Go through topTargets and try to promote (see {@link #canPromote}) one of them.
*
@@ -1842,7 +1851,7 @@
@TransitionInfo.TransitionMode
int getTransitMode(@NonNull WindowContainer wc) {
if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
- return TRANSIT_CLOSE;
+ return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
}
final boolean nowVisible = wc.isVisibleRequested();
if (nowVisible == mVisible) {
@@ -1879,12 +1888,10 @@
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
parentTask = record.getTask();
- if (record.mUseTransferredAnimation) {
- flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
- }
if (record.mVoiceInteraction) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
+ flags |= record.mTransitionChangeFlags;
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e1477d..949fa96 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -46,6 +46,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -242,8 +243,18 @@
}
@Override
- public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+ return startTransition(type, null /* transitionToken */, t);
+ }
+
+ @Override
+ public void startTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
+ startTransition(-1 /* unused type */, transitionToken, t);
+ }
+
+ private IBinder startTransition(@WindowManager.TransitionType int type,
+ @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
enforceTaskPermission("startTransition()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
@@ -1557,10 +1568,6 @@
return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
}
- private void enforceTaskPermission(String func) {
- mService.enforceTaskPermission(func);
- }
-
private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
.isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 42d2861..f61bccf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3869,7 +3869,7 @@
// configuration update when the window has requested to be hidden. Doing so can lead to
// the client erroneously accepting a configuration that would have otherwise caused an
// activity restart. We instead hand back the last reported {@link MergedConfiguration}.
- if (useLatestConfig || (relayoutVisible && (shouldCheckTokenVisibleRequested()
+ if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
|| mToken.isVisibleRequested()))) {
final Configuration globalConfig = getProcessGlobalConfiguration();
final Configuration overrideConfig = getMergedOverrideConfiguration();
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 267cff6..4a51b83 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -351,6 +351,35 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:sequence>
+ <!-- Thresholds as tenths of percent of current brightness level, at each level of
+ brightness -->
+ <xs:element name="brightnessThresholdPoints" type="thresholdPoints" maxOccurs="1" minOccurs="0">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="thresholdPoints">
+ <xs:sequence>
+ <xs:element type="thresholdPoint" name="brightnessThresholdPoint" maxOccurs="unbounded" minOccurs="1">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="thresholdPoint">
+ <xs:sequence>
+ <xs:element type="nonNegativeDecimal" name="threshold">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="percentage">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
</xs:complexType>
<xs:complexType name="autoBrightness">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f8bff75..748ef4b 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -13,7 +13,9 @@
public class BrightnessThresholds {
ctor public BrightnessThresholds();
+ method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
method @NonNull public final java.math.BigDecimal getMinimum();
+ method public final void setBrightnessThresholdPoints(com.android.server.display.config.ThresholdPoints);
method public final void setMinimum(@NonNull java.math.BigDecimal);
}
@@ -204,6 +206,19 @@
method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
}
+ public class ThresholdPoint {
+ ctor public ThresholdPoint();
+ method @NonNull public final java.math.BigDecimal getPercentage();
+ method @NonNull public final java.math.BigDecimal getThreshold();
+ method public final void setPercentage(@NonNull java.math.BigDecimal);
+ method public final void setThreshold(@NonNull java.math.BigDecimal);
+ }
+
+ public class ThresholdPoints {
+ ctor public ThresholdPoints();
+ method @NonNull public final java.util.List<com.android.server.display.config.ThresholdPoint> getBrightnessThresholdPoint();
+ }
+
public class Thresholds {
ctor public Thresholds();
method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6aca14f0..538d6bc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7783,6 +7783,7 @@
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+ Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
@@ -7823,6 +7824,7 @@
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+ Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..4f2b613 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -422,13 +422,13 @@
@Test
public void testHysteresisLevels() {
- int[] ambientBrighteningThresholds = {100, 200};
- int[] ambientDarkeningThresholds = {400, 500};
- int[] ambientThresholdLevels = {500};
+ float[] ambientBrighteningThresholds = {50, 100};
+ float[] ambientDarkeningThresholds = {10, 20};
+ float[] ambientThresholdLevels = {0, 500};
float ambientDarkeningMinChangeThreshold = 3.0f;
float ambientBrighteningMinChangeThreshold = 1.5f;
HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
- ambientDarkeningThresholds, ambientThresholdLevels,
+ ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
// test low, activate minimum change thresholds.
@@ -437,16 +437,17 @@
assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
// test max
- assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
- assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+ // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+ assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+ assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
// test just below threshold
- assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
- assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+ assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+ assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
// test at (considered above) threshold
- assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
- assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+ assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+ assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad..a719f52 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,6 +50,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayDeviceConfigTest {
private DisplayDeviceConfig mDisplayDeviceConfig;
+ private static final float ZERO_DELTA = 0.0f;
+ private static final float SMALL_DELTA = 0.0001f;
+
@Mock
private Context mContext;
@@ -69,26 +74,74 @@
assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
- assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
- 0.0f);
- assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+ ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
+ ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
- 0.0f);
- assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
- assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+ ZERO_DELTA);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+ float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{45.32f, 75.43f}, 0.0f);
+ float[]{45.32f, 75.43f}, ZERO_DELTA);
+
+ // Test thresholds
+ assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
+ ZERO_DELTA);
+ assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+ ZERO_DELTA);
+ assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 0.10f, 0.20f},
+ mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{9, 10, 11},
+ mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 0.11f, 0.21f},
+ mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{11, 12, 13},
+ mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 100, 200},
+ mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{13, 14, 15},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 300, 400},
+ mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{15, 16, 17},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 0.12f, 0.22f},
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{17, 18, 19},
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 0.13f, 0.23f},
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{19, 20, 21},
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 500, 600},
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{21, 22, 23},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 700, 800},
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{23, 24, 25},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -97,9 +150,60 @@
public void testConfigValuesFromConfigResource() {
setupDisplayDeviceConfigFromConfigResourceFile();
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+ float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+ float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+
+ // Test thresholds
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+ ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ // screen levels will be considered "old screen brightness scale"
+ // and therefore will divide by 255
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{35, 36, 37},
+ mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{37, 38, 39},
+ mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{27, 28, 29},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{29, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
+ assertArrayEquals(new float[]{35, 36, 37},
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
+ assertArrayEquals(new float[]{37, 38, 39},
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{27, 28, 29},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{29, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -154,11 +258,126 @@
+ "<ambientBrightnessChangeThresholds>\n"
+ "<brighteningThresholds>\n"
+ "<minimum>10</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>13</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>100</threshold><percentage>14</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>200</threshold><percentage>15</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ "</brighteningThresholds>\n"
+ "<darkeningThresholds>\n"
- + "<minimum>2</minimum>\n"
+ + "<minimum>30</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>15</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>300</threshold><percentage>16</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>400</threshold><percentage>17</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ "</darkeningThresholds>\n"
+ "</ambientBrightnessChangeThresholds>\n"
+ + "<displayBrightnessChangeThresholds>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>0.1</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold>\n"
+ + "<percentage>9</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.10</threshold>\n"
+ + "<percentage>10</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.20</threshold>\n"
+ + "<percentage>11</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>0.3</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>11</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.11</threshold><percentage>12</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.21</threshold><percentage>13</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</displayBrightnessChangeThresholds>\n"
+ + "<ambientBrightnessChangeThresholdsIdle>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>20</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>21</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>500</threshold><percentage>22</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>600</threshold><percentage>23</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>40</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>23</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>700</threshold><percentage>24</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>800</threshold><percentage>25</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</ambientBrightnessChangeThresholdsIdle>\n"
+ + "<displayBrightnessChangeThresholdsIdle>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>0.2</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>17</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.12</threshold><percentage>18</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.22</threshold><percentage>19</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>0.4</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>19</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.13</threshold><percentage>20</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.23</threshold><percentage>21</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</displayBrightnessChangeThresholdsIdle>\n"
+ "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+ "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease> "
+ "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
@@ -171,18 +390,6 @@
+ "</screenBrightnessRampDecreaseMaxMillis>"
+ "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+ "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
- + "<displayBrightnessChangeThresholds>"
- + "<brighteningThresholds>"
- + "<minimum>"
- + "0.001"
- + "</minimum>"
- + "</brighteningThresholds>"
- + "<darkeningThresholds>"
- + "<minimum>"
- + "0.002"
- + "</minimum>"
- + "</darkeningThresholds>"
- + "</displayBrightnessChangeThresholds>"
+ "<screenBrightnessRampIncreaseMaxMillis>"
+ "2000"
+ "</screenBrightnessRampIncreaseMaxMillis>\n"
@@ -241,8 +448,24 @@
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(screenBrightnessLevelLux);
- mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+ // Thresholds
+ // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
+ // of length N+1
+ when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
+ .thenReturn(new int[]{30, 31});
+ when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
+ .thenReturn(new int[]{42, 43});
+ when(mResources.getIntArray(
+ com.android.internal.R.array.config_ambientBrighteningThresholds))
+ .thenReturn(new int[]{270, 280, 290});
+ when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
+ .thenReturn(new int[]{290, 300, 310});
+ when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
+ .thenReturn(new int[]{350, 360, 370});
+ when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
+ .thenReturn(new int[]{370, 380, 390});
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
}
private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
new file mode 100644
index 0000000..246aa90
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.BatteryState.STATUS_CHARGING
+import android.hardware.BatteryState.STATUS_FULL
+import android.hardware.input.IInputDeviceBatteryListener
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.os.Binder
+import android.os.IBinder
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.notNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link InputDeviceBatteryController}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputDeviceBatteryControllerTests
+ */
+@Presubmit
+class BatteryControllerTests {
+ companion object {
+ const val PID = 42
+ const val DEVICE_ID = 13
+ const val SECOND_DEVICE_ID = 11
+ }
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+ @Mock
+ private lateinit var iInputManager: IInputManager
+
+ private lateinit var batteryController: BatteryController
+ private lateinit var context: Context
+
+ @Before
+ fun setup() {
+ context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ val inputManager = InputManager.resetInstance(iInputManager)
+ `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
+ `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID))
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(createInputDevice(DEVICE_ID))
+ `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID))
+ .thenReturn(createInputDevice(SECOND_DEVICE_ID))
+
+ batteryController = BatteryController(context, native)
+ }
+
+ private fun createInputDevice(deviceId: Int): InputDevice =
+ InputDevice(deviceId, 0 /*generation*/, 0 /*controllerNumber*/,
+ "Device $deviceId" /*name*/, 0 /*vendorId*/, 0 /*productId*/, "descriptor$deviceId",
+ true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/, null /*keyCharacterMap*/,
+ false /*hasVibrator*/, false /*hasMicrophone*/, false /*hasButtonUnderPad*/,
+ false /*hasSensor*/, true /*hasBattery*/)
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ private fun createMockListener(): IInputDeviceBatteryListener {
+ val listener = mock(IInputDeviceBatteryListener::class.java)
+ val binder = mock(Binder::class.java)
+ `when`(listener.asBinder()).thenReturn(binder)
+ return listener
+ }
+
+ @Test
+ fun testRegisterAndUnregisterBinderLifecycle() {
+ val listener = createMockListener()
+ // Ensure the binder lifecycle is tracked when registering a listener.
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder()).linkToDeath(notNull(), anyInt())
+ batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+ verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt())
+
+ // Ensure the binder lifecycle stops being tracked when all devices stopped being monitored.
+ batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID)
+ verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt())
+ batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+ }
+
+ @Test
+ fun testOneListenerPerProcess() {
+ val listener1 = createMockListener()
+ batteryController.registerBatteryListener(DEVICE_ID, listener1, PID)
+ verify(listener1.asBinder()).linkToDeath(notNull(), anyInt())
+
+ // If a second listener is added for the same process, a security exception is thrown.
+ val listener2 = createMockListener()
+ try {
+ batteryController.registerBatteryListener(DEVICE_ID, listener2, PID)
+ fail("Expected security exception when registering more than one listener per process")
+ } catch (ignored: SecurityException) {
+ }
+ }
+
+ @Test
+ fun testProcessDeathRemovesListener() {
+ val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt())
+
+ // When the binder dies, the callback is unregistered.
+ deathRecipient.value!!.binderDied(listener.asBinder())
+ verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+
+ // It is now possible to register the same listener again.
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt())
+ }
+
+ @Test
+ fun testRegisteringListenerNotifiesStateImmediately() {
+ `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL)
+ `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
+ eq(STATUS_FULL), eq(1f), anyLong())
+
+ `when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING)
+ `when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78)
+ batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+ verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/),
+ eq(STATUS_CHARGING), eq(0.78f), anyLong())
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 617a34f..91c2fe0 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -54,6 +54,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -83,7 +84,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
import android.test.mock.MockContentResolver;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -101,6 +101,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Spy;
import java.time.LocalDateTime;
import java.time.LocalTime;
@@ -137,8 +138,8 @@
private PackageManager mPackageManager;
@Mock
private IBinder mBinder;
- @Mock
- private DreamManagerInternal mDreamManager;
+ @Spy
+ private TestInjector mInjector;
@Captor
private ArgumentCaptor<Intent> mOrderedBroadcastIntent;
@Captor
@@ -207,10 +208,10 @@
addLocalService(WindowManagerInternal.class, mWindowManager);
addLocalService(PowerManagerInternal.class, mLocalPowerManager);
addLocalService(TwilightManager.class, mTwilightManager);
- addLocalService(DreamManagerInternal.class, mDreamManager);
-
+
+ mInjector = spy(new TestInjector());
mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
- mTwilightManager, new TestInjector());
+ mTwilightManager, mInjector);
try {
mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
} catch (SecurityException e) {/* ignore for permission denial */}
@@ -1321,84 +1322,53 @@
@Test
public void dreamWhenDocked() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
-
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
- }
-
- @Test
- public void noDreamWhenDocked_dreamsDisabled() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(false);
-
- triggerDockIntent();
- verifyAndSendResultBroadcast();
- verify(mDreamManager, never()).requestDream();
- }
-
- @Test
- public void noDreamWhenDocked_dreamsWhenDockedDisabled() {
- setScreensaverActivateOnDock(false);
- setScreensaverEnabled(true);
-
- triggerDockIntent();
- verifyAndSendResultBroadcast();
- verify(mDreamManager, never()).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void noDreamWhenDocked_keyguardNotShowing_interactive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
when(mPowerManager.isInteractive()).thenReturn(true);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager, never()).requestDream();
+ verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void dreamWhenDocked_keyguardShowing_interactive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
when(mPowerManager.isInteractive()).thenReturn(false);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void dreamWhenDocked_keyguardNotShowing_notInteractive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
when(mPowerManager.isInteractive()).thenReturn(false);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void dreamWhenDocked_keyguardShowing_notInteractive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
when(mPowerManager.isInteractive()).thenReturn(false);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
private void triggerDockIntent() {
@@ -1435,22 +1405,6 @@
mOrderedBroadcastIntent.getValue());
}
- private void setScreensaverEnabled(boolean enable) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.SCREENSAVER_ENABLED,
- enable ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- private void setScreensaverActivateOnDock(boolean enable) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
- enable ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
private void requestAllPossibleProjectionTypes() throws RemoteException {
for (int i = 0; i < Integer.SIZE; ++i) {
mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);
@@ -1467,11 +1421,17 @@
}
public TestInjector(int callingUid) {
- this.callingUid = callingUid;
+ this.callingUid = callingUid;
}
+ @Override
public int getCallingUid() {
return callingUid;
}
+
+ @Override
+ public void startDreamWhenDockedIfAppropriate(Context context) {
+ // do nothing
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c..0462e1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Process.NOBODY_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1220,20 +1221,34 @@
@Test
public void testCreateRecentTaskInfo_detachedTask() {
- final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+ final Task task = createTaskBuilder(".Task").build();
+ new ActivityBuilder(mSupervisor.mService)
+ .setTask(task)
+ .setUid(NOBODY_UID)
+ .setComponent(getUniqueComponentName())
+ .build();
final TaskDisplayArea tda = task.getDisplayArea();
assertTrue(task.isAttached());
assertTrue(task.supportsMultiWindow());
- RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+ RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ false /* getTasksAllowed */);
+
+ assertTrue(info.topActivity == null);
+ assertTrue(info.topActivityInfo == null);
+ assertTrue(info.baseActivity == null);
+
// The task can be put in split screen even if it is not attached now.
task.removeImmediately();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
@@ -1242,7 +1257,8 @@
doReturn(false).when(tda).supportsNonResizableMultiWindow();
doReturn(false).when(task).isResizeable();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertFalse(info.supportsMultiWindow);
@@ -1250,7 +1266,8 @@
// the device supports it.
doReturn(true).when(tda).supportsNonResizableMultiWindow();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 6128428..b46e90d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1312,13 +1312,15 @@
secondActivity.app.setThread(null);
// This should do nothing from a non-attached caller.
assertFalse(task.navigateUpTo(secondActivity /* source record */,
- firstActivity.intent /* destIntent */, null /* destGrants */,
- 0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+ firstActivity.intent /* destIntent */, null /* resolvedType */,
+ null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+ null /* resultGrants */));
secondActivity.app.setThread(thread);
assertTrue(task.navigateUpTo(secondActivity /* source record */,
- firstActivity.intent /* destIntent */, null /* destGrants */,
- 0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+ firstActivity.intent /* destIntent */, null /* resolvedType */,
+ null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+ null /* resultGrants */));
// The firstActivity uses default launch mode, so the activities between it and itself will
// be finished.
assertTrue(secondActivity.finishing);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 1404de2..0b23359 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -195,6 +195,7 @@
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
+ assertTaskFragmentParentInfoChangedTransaction(mTask);
assertTaskFragmentAppearedTransaction();
}
@@ -365,6 +366,7 @@
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
+ assertTaskFragmentParentInfoChangedTransaction(task);
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
}
@@ -1205,7 +1207,8 @@
/**
* Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
- * {@link WindowOrganizerController#applyTransaction} to apply the transaction,
+ * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
+ * transaction,
*/
private void createTaskFragmentFromOrganizer(WindowContainerTransaction wct,
ActivityRecord ownerActivity, IBinder fragmentToken) {
@@ -1239,8 +1242,8 @@
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- // Appeared will come with parent info changed.
- final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1253,8 +1256,8 @@
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- // InfoChanged may come with parent info changed.
- final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1266,7 +1269,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_VANISHED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1278,7 +1283,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo());
@@ -1290,7 +1297,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_ERROR, change.getType());
assertEquals(mErrorToken, change.getErrorCallbackToken());
final Bundle errorBundle = change.getErrorBundle();
@@ -1306,7 +1315,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(taskId, change.getTaskId());
assertEquals(intent, change.getActivityIntent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 76cd19b..68ac1d6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1454,6 +1454,21 @@
verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean());
}
+ @Test
+ public void testGetTaskFragment() {
+ final Task parentTask = createTask(mDisplayContent);
+ final TaskFragment tf0 = createTaskFragmentWithParentTask(parentTask);
+ final TaskFragment tf1 = createTaskFragmentWithParentTask(parentTask);
+
+ assertNull("Could not find it because there's no organized TaskFragment",
+ parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
+
+ doReturn(true).when(tf0).isOrganizedTaskFragment();
+
+ assertEquals("tf0 must be return because it's the organized TaskFragment.",
+ tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 4435d62..7342c49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -393,6 +392,70 @@
}
@Test
+ public void testCreateInfo_PromoteSimilarClose() {
+ final Transition transition = createTestTransition(TRANSIT_CLOSE);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task topTask = createTask(mDisplayContent);
+ final Task belowTask = createTask(mDisplayContent);
+ final ActivityRecord showing = createActivityRecord(belowTask);
+ final ActivityRecord hiding = createActivityRecord(topTask);
+ final ActivityRecord closing = createActivityRecord(topTask);
+ // Start states.
+ changes.put(topTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(belowTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(hiding, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ fillChangeMap(changes, topTask);
+ // End states.
+ showing.mVisibleRequested = true;
+ closing.mVisibleRequested = false;
+ hiding.mVisibleRequested = false;
+
+ participants.add(belowTask);
+ participants.add(hiding);
+ participants.add(closing);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ assertEquals(2, targets.size());
+ assertTrue(targets.contains(belowTask));
+ assertTrue(targets.contains(topTask));
+ }
+
+ @Test
+ public void testCreateInfo_PromoteSimilarOpen() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task topTask = createTask(mDisplayContent);
+ final Task belowTask = createTask(mDisplayContent);
+ final ActivityRecord showing = createActivityRecord(topTask);
+ final ActivityRecord opening = createActivityRecord(topTask);
+ final ActivityRecord closing = createActivityRecord(belowTask);
+ // Start states.
+ changes.put(topTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(belowTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ fillChangeMap(changes, topTask);
+ // End states.
+ showing.mVisibleRequested = true;
+ opening.mVisibleRequested = true;
+ closing.mVisibleRequested = false;
+
+ participants.add(belowTask);
+ participants.add(showing);
+ participants.add(opening);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ assertEquals(2, targets.size());
+ assertTrue(targets.contains(belowTask));
+ assertTrue(targets.contains(topTask));
+ }
+
+ @Test
public void testTargets_noIntermediatesToWallpaper() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
@@ -1165,10 +1228,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
@@ -1210,10 +1271,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
@@ -1241,10 +1300,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
activity.mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index b8da8cc..5c1c193 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -43,6 +43,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -195,14 +196,25 @@
mWm.mWindowMap.put(win.mClient.asBinder(), win);
final int w = 100;
final int h = 200;
+ final ClientWindowFrames outFrames = new ClientWindowFrames();
+ final MergedConfiguration outConfig = new MergedConfiguration();
+ final SurfaceControl outSurfaceControl = new SurfaceControl();
+ final InsetsState outInsetsState = new InsetsState();
+ final InsetsSourceControl[] outControls = new InsetsSourceControl[0];
+ final Bundle outBundle = new Bundle();
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
- new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
- new InsetsState(), new InsetsSourceControl[0], new Bundle());
+ outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
// Because the window is already invisible, it doesn't need to apply exiting animation
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
assertNull(win.mWinAnimator.mSurfaceController);
+
+ doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction);
+ // Invisible requested activity should not get the last config even if its view is visible.
+ mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
+ outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
+ assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ef532f5..b99fd16 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -718,6 +718,10 @@
activity.mVisibleRequested = true;
}
+ static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask) {
+ return createTaskFragmentWithParentTask(parentTask, false /* createEmbeddedTask */);
+ }
+
/**
* Creates a {@link TaskFragment} and attach it to the {@code parentTask}.
*
@@ -1727,7 +1731,7 @@
}
void startTransition() {
- mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
+ mOrganizer.startTransition(mLastTransit, null);
}
void onTransactionReady(SurfaceControl.Transaction t) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
new file mode 100644
index 0000000..efb92f2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.provider.Settings
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assert.assertNotNull
+
+class AssistantAppHelper @JvmOverloads constructor(
+ val instr: Instrumentation,
+ val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
+) {
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
+ protected val defaultAssistant: String? = Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT)
+ protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE)
+
+ fun setDefaultAssistant() {
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ component.flattenToString())
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT,
+ component.flattenToString())
+ }
+
+ fun resetDefaultAssistant() {
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ defaultVoiceInteractionService)
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT,
+ defaultAssistant)
+ }
+
+ /**
+ * Open Assistance UI.
+ *
+ * @param longpress open the UI by long pressing power button.
+ * Otherwise open the UI through vioceinteraction shell command directly.
+ */
+ @JvmOverloads
+ fun openUI(longpress: Boolean = false) {
+ if (longpress) {
+ uiDevice.executeShellCommand("input keyevent --longpress KEYCODE_POWER")
+ } else {
+ uiDevice.executeShellCommand("cmd voiceinteraction show")
+ }
+ val ui = uiDevice.wait(
+ Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+ FIND_TIMEOUT)
+ assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 45a4730..e90eed1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -80,20 +80,21 @@
public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".NotificationActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".NotificationActivity");
public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
- public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
- FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+ public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".GameActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".GameActivity");
+
+ public static final ComponentName ASSISTANT_SERVICE_COMPONENT_NAME =
+ new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".AssistantInteractionService");
}