Merge "[Partial Screensharing] Add a temporary entrypoint in SysUI screen recorder" into tm-qpr-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8bfb1ae..da5b88b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3350,7 +3350,8 @@
method @NonNull public java.util.concurrent.Executor getExecutor();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
- method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+ method @Deprecated public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+ method public void onTaskFragmentError(@NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 1769993e..f97415c 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -20,7 +20,6 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -67,7 +66,7 @@
private CharSequence mName;
private String mDescription;
private boolean mBlocked;
- private ParceledListSlice<NotificationChannel> mChannels;
+ private List<NotificationChannel> mChannels = new ArrayList<>();
// Bitwise representation of fields that have been changed by the user
private int mUserLockedFields;
@@ -101,8 +100,7 @@
} else {
mDescription = null;
}
- mChannels = in.readParcelable(
- NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class);
+ in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
mBlocked = in.readBoolean();
mUserLockedFields = in.readInt();
}
@@ -129,7 +127,7 @@
} else {
dest.writeByte((byte) 0);
}
- dest.writeParcelable(mChannels, flags);
+ dest.writeParcelableList(mChannels, flags);
dest.writeBoolean(mBlocked);
dest.writeInt(mUserLockedFields);
}
@@ -159,7 +157,7 @@
* Returns the list of channels that belong to this group
*/
public List<NotificationChannel> getChannels() {
- return mChannels == null ? new ArrayList<>() : mChannels.getList();
+ return mChannels;
}
/**
@@ -193,8 +191,15 @@
/**
* @hide
*/
+ public void addChannel(NotificationChannel channel) {
+ mChannels.add(channel);
+ }
+
+ /**
+ * @hide
+ */
public void setChannels(List<NotificationChannel> channels) {
- mChannels = new ParceledListSlice<>(channels);
+ mChannels = channels;
}
/**
@@ -329,7 +334,7 @@
proto.write(NotificationChannelGroupProto.NAME, mName.toString());
proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
- for (NotificationChannel channel : mChannels.getList()) {
+ for (NotificationChannel channel : mChannels) {
channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS);
}
proto.end(token);
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 99e4feb..943eee4 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -157,6 +157,11 @@
int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
/**
+ * A power press stopped this biometric operation.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index fd46f24..2b62b98 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -70,6 +70,7 @@
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_ERROR_RE_ENROLL,
FACE_ERROR_UNKNOWN,
+ BIOMETRIC_ERROR_POWER_PRESSED,
})
@Retention(RetentionPolicy.SOURCE)
@interface FaceError {}
@@ -184,6 +185,12 @@
int FACE_ERROR_UNKNOWN = 17;
/**
+ * A power press stopped this biometric operation.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+
+ /**
* Vendor codes received from the HAL start at 0. Codes that the framework exposes to keyguard
* append this value for some reason. We should probably remove this and just send the actual
* vendor code.
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index d8ebb62..98f571b 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -61,7 +61,8 @@
BIOMETRIC_ERROR_RE_ENROLL,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
FINGERPRINT_ERROR_UNKNOWN,
- FINGERPRINT_ERROR_BAD_CALIBRATION})
+ FINGERPRINT_ERROR_BAD_CALIBRATION,
+ BIOMETRIC_ERROR_POWER_PRESSED})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintError {}
@@ -188,6 +189,12 @@
int FINGERPRINT_ERROR_BAD_CALIBRATION = 18;
/**
+ * A power press stopped this biometric operation.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+
+ /**
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/hardware/biometrics/BiometricStateListener.java b/core/java/android/hardware/biometrics/BiometricStateListener.java
index 2ac0c1e..b167cc6 100644
--- a/core/java/android/hardware/biometrics/BiometricStateListener.java
+++ b/core/java/android/hardware/biometrics/BiometricStateListener.java
@@ -46,6 +46,14 @@
public @interface State {
}
+ // The sensor received a touch.
+ public static final int ACTION_SENSOR_TOUCH = 0;
+
+ @IntDef({ACTION_SENSOR_TOUCH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {
+ }
+
/**
* Defines behavior in response to state update
* @param newState new state of the biometric sensor
@@ -53,6 +61,13 @@
public void onStateChanged(@BiometricStateListener.State int newState) {
}
+
+ /**
+ * Invoked when a biometric action has occurred.
+ */
+ public void onBiometricAction(@BiometricStateListener.Action int action) {
+ }
+
/**
* Invoked when enrollment state changes for the specified user
*/
diff --git a/core/java/android/hardware/biometrics/IBiometricStateListener.aidl b/core/java/android/hardware/biometrics/IBiometricStateListener.aidl
index 5bdced0..6bb170d 100644
--- a/core/java/android/hardware/biometrics/IBiometricStateListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricStateListener.aidl
@@ -22,5 +22,6 @@
*/
oneway interface IBiometricStateListener {
void onStateChanged(int newState);
+ void onBiometricAction(int action);
void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 8db298f..861a850 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1325,8 +1325,11 @@
* flashlight brightness level via
* {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
* If this value is equal to 1, flashlight brightness control is not supported.
- * The value for this key will be null for devices with no flash unit.
- * This level must be set to a safe value to prevent any burn out issues.</p>
+ * The value for this key will be null for devices with no flash unit.</p>
+ * <p>The maximum value is guaranteed to be safe to use for an indefinite duration in
+ * terms of device flashlight lifespan, but may be too bright for comfort for many
+ * use cases. Use the default torch brightness value to avoid problems with an
+ * over-bright flashlight.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*/
@PublicKey
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 28f1f02..c614cdb 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -101,6 +101,7 @@
private static final int MSG_FINGERPRINT_DETECTED = 107;
private static final int MSG_UDFPS_POINTER_DOWN = 108;
private static final int MSG_UDFPS_POINTER_UP = 109;
+ private static final int MSG_POWER_BUTTON_PRESSED = 110;
/**
* @hide
@@ -984,6 +985,16 @@
}
/**
+ * This is triggered by SideFpsEventHandler
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onPowerPressed() {
+ Slog.i(TAG, "onPowerPressed");
+ mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
+ }
+
+ /**
* Determine if there is at least one fingerprint enrolled.
*
* @return true if at least one fingerprint is enrolled, false otherwise
@@ -1196,6 +1207,9 @@
case MSG_UDFPS_POINTER_UP:
sendUdfpsPointerUp(msg.arg1 /* sensorId */);
break;
+ case MSG_POWER_BUTTON_PRESSED:
+ sendPowerPressed();
+ break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
@@ -1325,6 +1339,14 @@
mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
+ private void sendPowerPressed() {
+ try {
+ mService.onPowerPressed();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending power press", e);
+ }
+ }
+
/**
* @hide
*/
@@ -1461,6 +1483,9 @@
case FINGERPRINT_ERROR_BAD_CALIBRATION:
return context.getString(
com.android.internal.R.string.fingerprint_error_bad_calibration);
+ case BIOMETRIC_ERROR_POWER_PRESSED:
+ return context.getString(
+ com.android.internal.R.string.fingerprint_error_power_pressed);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 0b63446..20cc58c 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -171,4 +171,7 @@
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
+
+ // Sends a power button pressed event to all listeners.
+ oneway void onPowerPressed();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7964f7c..ce35461 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9704,6 +9704,43 @@
public static final String FACE_UNLOCK_RE_ENROLL = "face_unlock_re_enroll";
/**
+ * The time (in millis) to wait for a power button before sending a
+ * successful auth in to keyguard(for side fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW =
+ "fingerprint_side_fps_kg_power_window";
+
+ /**
+ * The time (in millis) to wait for a power button before sending
+ * a successful auth in biometric prompt(for side fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW =
+ "fingerprint_side_fps_bp_power_window";
+
+ /**
+ * The time (in millis) that a finger tap will wait for a power button
+ * before dismissing the power dialog during enrollment(for side
+ * fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW =
+ "fingerprint_side_fps_enroll_tap_window";
+
+ /**
+ * The time (in millis) that a power event will ignore future authentications
+ * (for side fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME =
+ "fingerprint_side_fps_auth_downtime";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
@@ -10880,6 +10917,14 @@
public static final String ADAPTIVE_CHARGING_ENABLED = "adaptive_charging_enabled";
/**
+ * Whether battery saver is currently set to different schedule mode.
+ *
+ * @hide
+ */
+ public static final String EXTRA_AUTOMATIC_POWER_SAVE_MODE =
+ "extra_automatic_power_save_mode";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
@@ -11301,6 +11346,7 @@
* <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li>
* <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li>
* <li>{@link BatteryManager#BATTERY_PLUGGED_WIRELESS} to stay on for wireless charger</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_DOCK} to stay on for dock charger</li>
* </ul>
* These values can be OR-ed together.
*/
diff --git a/core/java/android/util/SafetyProtectionUtils.java b/core/java/android/util/SafetyProtectionUtils.java
new file mode 100644
index 0000000..af985c5
--- /dev/null
+++ b/core/java/android/util/SafetyProtectionUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+
+/**
+ * Util class for whether we should show the safety protection resources.
+ *
+ * @hide
+ */
+public class SafetyProtectionUtils {
+ private static final String SAFETY_PROTECTION_RESOURCES_ENABLED = "safety_protection_enabled";
+
+ /**
+ * Determines whether we should show the safety protection resources.
+ * We show the resources only if
+ * (1) the feature flag safety_protection_enabled is enabled and
+ * (2) the config value config_safetyProtectionEnabled is enabled/true and
+ * (3) the resources exist (currently the resources only exist on GMS devices)
+ *
+ * TODO: make this an API in U
+ *
+ * @hide
+ */
+ public static boolean shouldShowSafetyProtectionResources(Context context) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_PROTECTION_RESOURCES_ENABLED, false)
+ && context.getResources().getBoolean(
+ Resources.getSystem()
+ .getIdentifier("config_safetyProtectionEnabled",
+ "bool", "android"))
+ && context.getDrawable(android.R.drawable.ic_safety_protection) != null
+ && context.getString(android.R.string.safety_protection_display_text) != null
+ && !context.getString(android.R.string.safety_protection_display_text).isEmpty();
+ }
+}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b17e199..d63c25a 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -25,7 +25,6 @@
import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
-import static android.view.InsetsSourceControl.INVALID_HINTS;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.getDefaultVisibility;
import static android.view.InsetsState.toPublicType;
@@ -34,7 +33,6 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.util.ArraySet;
import android.util.Log;
@@ -94,13 +92,6 @@
private Rect mPendingVisibleFrame;
/**
- * Indicates if we have the pending animation. When we have the control, we need to play the
- * animation if the requested visibility is different from the current state. But if we haven't
- * had a leash yet, we will set this flag, and play the animation once we get the leash.
- */
- private boolean mIsAnimationPending;
-
- /**
* @param type The {@link InternalInsetsType} of the consumed insets.
* @param state The current {@link InsetsState} of the consumed insets.
* @param transactionSupplier The source of new {@link Transaction} instances. The supplier
@@ -138,7 +129,6 @@
}
return false;
}
- SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
final InsetsSourceControl lastControl = mSourceControl;
mSourceControl = control;
@@ -163,27 +153,21 @@
// For updateCompatSysUiVisibility
applyLocalVisibilityOverride();
} else {
- // We are gaining control, and need to run an animation since previous state
- // didn't match
final boolean requestedVisible = isRequestedVisibleAwaitingControl();
- final boolean fakeControl = INVALID_HINTS.equals(control.getInsetsHint());
- final boolean needsAnimation = requestedVisible != mState.getSource(mType).isVisible()
- && !fakeControl;
- if (control.getLeash() != null && (needsAnimation || mIsAnimationPending)) {
- if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b",
+ final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
+ final SurfaceControl newLeash = control.getLeash();
+ if (newLeash != null && (oldLeash == null || !newLeash.isSameSurface(oldLeash))
+ && requestedVisible != control.isInitiallyVisible()) {
+ // We are gaining leash, and need to run an animation since previous state
+ // didn't match.
+ if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
mController.getHost().getRootViewTitle(), requestedVisible));
if (requestedVisible) {
showTypes[0] |= toPublicType(getType());
} else {
hideTypes[0] |= toPublicType(getType());
}
- mIsAnimationPending = false;
} else {
- if (needsAnimation) {
- // We need animation but we haven't had a leash yet. Set this flag that when we
- // get the leash we can play the deferred animation.
- mIsAnimationPending = true;
- }
// We are gaining control, but don't need to run an animation.
// However make sure that the leash visibility is still up to date.
if (applyLocalVisibilityOverride()) {
@@ -195,7 +179,7 @@
applyRequestedVisibilityToControl();
// Remove the surface that owned by last control when it lost.
- if (!requestedVisible && !mIsAnimationPending && lastControl == null) {
+ if (!requestedVisible && lastControl == null) {
removeSurface();
}
}
@@ -406,16 +390,6 @@
protected void setRequestedVisible(boolean requestedVisible) {
if (mRequestedVisible != requestedVisible) {
mRequestedVisible = requestedVisible;
-
- // We need an animation later if the leash of a real control (which has an insets hint)
- // is not ready. The !mIsAnimationPending check is in case that the requested visibility
- // is changed twice before playing the animation -- we don't need an animation in this
- // case.
- mIsAnimationPending = !mIsAnimationPending
- && mSourceControl != null
- && mSourceControl.getLeash() == null
- && !Insets.NONE.equals(mSourceControl.getInsetsHint());
-
mController.onRequestedVisibilityChanged(this);
if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 2cf827d..5f1cbba 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -31,6 +31,7 @@
import android.view.InsetsState.InternalInsetsType;
import java.io.PrintWriter;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -39,10 +40,9 @@
*/
public class InsetsSourceControl implements Parcelable {
- public static final Insets INVALID_HINTS = Insets.of(-1, -1, -1, -1);
-
private final @InternalInsetsType int mType;
private final @Nullable SurfaceControl mLeash;
+ private final boolean mInitiallyVisible;
private final Point mSurfacePosition;
// This is used while playing an insets animation regardless of the relative frame. This would
@@ -53,9 +53,10 @@
private int mParcelableFlags;
public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash,
- Point surfacePosition, Insets insetsHint) {
+ boolean initiallyVisible, Point surfacePosition, Insets insetsHint) {
mType = type;
mLeash = leash;
+ mInitiallyVisible = initiallyVisible;
mSurfacePosition = surfacePosition;
mInsetsHint = insetsHint;
}
@@ -67,6 +68,7 @@
} else {
mLeash = null;
}
+ mInitiallyVisible = other.mInitiallyVisible;
mSurfacePosition = new Point(other.mSurfacePosition);
mInsetsHint = other.mInsetsHint;
mSkipAnimationOnce = other.getAndClearSkipAnimationOnce();
@@ -75,6 +77,7 @@
public InsetsSourceControl(Parcel in) {
mType = in.readInt();
mLeash = in.readTypedObject(SurfaceControl.CREATOR);
+ mInitiallyVisible = in.readBoolean();
mSurfacePosition = in.readTypedObject(Point.CREATOR);
mInsetsHint = in.readTypedObject(Insets.CREATOR);
mSkipAnimationOnce = in.readBoolean();
@@ -94,6 +97,10 @@
return mLeash;
}
+ public boolean isInitiallyVisible() {
+ return mInitiallyVisible;
+ }
+
public boolean setSurfacePosition(int left, int top) {
if (mSurfacePosition.equals(left, top)) {
return false;
@@ -148,6 +155,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeTypedObject(mLeash, mParcelableFlags);
+ dest.writeBoolean(mInitiallyVisible);
dest.writeTypedObject(mSurfacePosition, mParcelableFlags);
dest.writeTypedObject(mInsetsHint, mParcelableFlags);
dest.writeBoolean(mSkipAnimationOnce);
@@ -172,6 +180,7 @@
return mType == that.mType
&& ((mLeash == thatLeash)
|| (mLeash != null && thatLeash != null && mLeash.isSameSurface(thatLeash)))
+ && mInitiallyVisible == that.mInitiallyVisible
&& mSurfacePosition.equals(that.mSurfacePosition)
&& mInsetsHint.equals(that.mInsetsHint)
&& mSkipAnimationOnce == that.mSkipAnimationOnce;
@@ -179,12 +188,8 @@
@Override
public int hashCode() {
- int result = mType;
- result = 31 * result + (mLeash != null ? mLeash.hashCode() : 0);
- result = 31 * result + mSurfacePosition.hashCode();
- result = 31 * result + mInsetsHint.hashCode();
- result = 31 * result + (mSkipAnimationOnce ? 1 : 0);
- return result;
+ return Objects.hash(mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
+ mSkipAnimationOnce);
}
@Override
@@ -200,6 +205,7 @@
pw.print(prefix);
pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
pw.print(" mLeash="); pw.print(mLeash);
+ pw.print(" mInitiallyVisible="); pw.print(mInitiallyVisible);
pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
pw.print(" mInsetsHint="); pw.print(mInsetsHint);
pw.print(" mSkipAnimationOnce="); pw.print(mSkipAnimationOnce);
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index dbf3570..ca57c84 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -32,6 +32,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -494,19 +495,39 @@
*/
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
- if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
- Toast result = new Toast(context, looper);
- result.mText = text;
- result.mDuration = duration;
- return result;
- } else {
- Toast result = new Toast(context, looper);
- View v = ToastPresenter.getTextToastView(context, text);
- result.mNextView = v;
- result.mDuration = duration;
+ Toast result = new Toast(context, looper);
- return result;
+ if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
+ result.mText = text;
+ } else {
+ result.mNextView = ToastPresenter.getTextToastView(context, text);
}
+
+ result.mDuration = duration;
+ return result;
+ }
+
+ /**
+ * Make a standard toast with an icon to display using the specified looper.
+ * If looper is null, Looper.myLooper() is used.
+ *
+ * The toast will be a custom view that's rendered by the app (instead of by SystemUI).
+ * In Android version R and above, non-system apps can only render the toast
+ * when it's in the foreground.
+ *
+ * @hide
+ */
+ public static Toast makeCustomToastWithIcon(@NonNull Context context, @Nullable Looper looper,
+ @NonNull CharSequence text, @Duration int duration, @NonNull Drawable icon) {
+ if (icon == null) {
+ throw new IllegalArgumentException("Drawable icon should not be null "
+ + "for makeCustomToastWithIcon");
+ }
+
+ Toast result = new Toast(context, looper);
+ result.mNextView = ToastPresenter.getTextToastViewWithIcon(context, text, icon);
+ result.mDuration = duration;
+ return result;
}
/**
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index eccff06..7467100 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -55,6 +56,8 @@
@VisibleForTesting
public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;
+ @VisibleForTesting
+ public static final int TEXT_TOAST_LAYOUT_WITH_ICON = R.layout.transient_notification_with_icon;
/**
* Returns the default text toast view for message {@code text}.
@@ -66,6 +69,24 @@
return view;
}
+ /**
+ * Returns the default icon text toast view for message {@code text} and the icon {@code icon}.
+ */
+ public static View getTextToastViewWithIcon(Context context, CharSequence text, Drawable icon) {
+ if (icon == null) {
+ return getTextToastView(context, text);
+ }
+
+ View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT_WITH_ICON, null);
+ TextView textView = view.findViewById(com.android.internal.R.id.message);
+ textView.setText(text);
+ ImageView imageView = view.findViewById(com.android.internal.R.id.icon);
+ if (imageView != null) {
+ imageView.setImageDrawable(icon);
+ }
+ return view;
+ }
+
private final Context mContext;
private final Resources mResources;
private final WindowManager mWindowManager;
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
index 8dfda7d..3335c9c 100644
--- a/core/java/android/window/ITaskFragmentOrganizer.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -45,10 +45,11 @@
*
* @param errorCallbackToken Token set through {@link
* WindowContainerTransaction#setErrorCallbackToken(IBinder)}
- * @param exceptionBundle Bundle containing the exception. Should be created with
- * {@link TaskFragmentOrganizer#putExceptionInBundle}.
+ * @param errorBundle Bundle containing the exception, operation type and TaskFragmentInfo
+ * if any. Should be created with
+ * {@link TaskFragmentOrganizer#putErrorInfoInBundle}.
*/
- void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle exceptionBundle);
+ void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle errorBundle);
/**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 2ef49c3..e4a6ad8 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -18,6 +18,7 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Intent;
import android.content.res.Configuration;
@@ -39,16 +40,23 @@
* Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}.
*/
private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception";
+ private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
+ private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
/**
- * Creates a {@link Bundle} with an exception that can be passed to
- * {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+ * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any)
+ * that can be passed to {@link ITaskFragmentOrganizer#onTaskFragmentError}.
* @hide
*/
- public static Bundle putExceptionInBundle(@NonNull Throwable exception) {
- final Bundle exceptionBundle = new Bundle();
- exceptionBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
- return exceptionBundle;
+ public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
+ @Nullable TaskFragmentInfo info, int opType) {
+ final Bundle errorBundle = new Bundle();
+ errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
+ if (info != null) {
+ errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
+ }
+ errorBundle.putInt(KEY_ERROR_CALLBACK_OP_TYPE, opType);
+ return errorBundle;
}
/**
@@ -151,11 +159,34 @@
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param exception exception from the server side.
+ *
+ * @deprecated Use {@link #onTaskFragmentError(IBinder, TaskFragmentInfo, int, Throwable)}
+ * instead.
*/
+ @Deprecated
public void onTaskFragmentError(
@NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
/**
+ * Called when the {@link WindowContainerTransaction} created with
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
+ *
+ * @param errorCallbackToken token set in
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+ * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
+ * TaskFragment created.
+ * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
+ * transaction operation.
+ * @param exception exception from the server side.
+ */
+ public void onTaskFragmentError(
+ @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
+ int opType, @NonNull Throwable exception) {
+ // Doing so to keep compatibility. This will be removed in the next release.
+ onTaskFragmentError(errorCallbackToken, exception);
+ }
+
+ /**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
* orginial Task. In this case, we need to notify the organizer so that it can check if the
@@ -217,10 +248,16 @@
@Override
public void onTaskFragmentError(
- @NonNull IBinder errorCallbackToken, @NonNull Bundle exceptionBundle) {
- mExecutor.execute(() -> TaskFragmentOrganizer.this.onTaskFragmentError(
- errorCallbackToken,
- (Throwable) exceptionBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION)));
+ @NonNull IBinder errorCallbackToken, @NonNull Bundle errorBundle) {
+ mExecutor.execute(() -> {
+ final TaskFragmentInfo info = errorBundle.getParcelable(
+ KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
+ TaskFragmentOrganizer.this.onTaskFragmentError(
+ errorCallbackToken, info,
+ errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
+ (Throwable) errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
+ java.lang.Throwable.class));
+ });
}
@Override
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 6a421f0..285258a 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -473,6 +473,13 @@
}
optional PowerMenuPrivacy power_menu_privacy = 81;
+ message ExtraLowPowerMode {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto extra_automatic_power_save_mode = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional ExtraLowPowerMode extra_low_power_mode = 93;
+
message PrintService {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -679,5 +686,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 93;
+ // Next tag = 94;
}
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 04f4d7b..bd4f990 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -238,6 +238,7 @@
optional bool is_stay_on_while_plugged_in_ac = 1;
optional bool is_stay_on_while_plugged_in_usb = 2;
optional bool is_stay_on_while_plugged_in_wireless = 3;
+ optional bool is_stay_on_while_plugged_in_dock = 4;
}
message ScreenBrightnessSettingLimitsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5e99d6c4..ce372e7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7068,6 +7068,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.notification.NotificationHistoryJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/layout/transient_notification_with_icon.xml b/core/res/res/layout/transient_notification_with_icon.xml
new file mode 100644
index 0000000..e9b17df
--- /dev/null
+++ b/core/res/res/layout/transient_notification_with_icon.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:maxWidth="@dimen/toast_width"
+ android:background="?android:attr/toastFrameBackground"
+ android:elevation="@dimen/toast_elevation"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:textAppearance="@style/TextAppearance.Toast"/>
+</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a8c7bf2..067dc86 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3498,6 +3498,22 @@
<!-- Specify if the fingerprint hardware support gestures-->
<bool name="config_fingerprintSupportsGestures">false</bool>
+ <!-- The time (in millis) to wait for a power button before sending
+ a successful auth in biometric prompt(for side fingerprint) -->
+ <integer name="config_sidefpsBpPowerPressWindow">300</integer>
+
+ <!-- The time (in millis) to wait for a power button before sending a
+ successful auth in to keyguard(for side fingerprint) -->
+ <integer name="config_sidefpsKeyguardPowerPressWindow">300</integer>
+
+ <!-- The time (in millis) that a power event will ignore future authentications
+ (for side fingerprint) -->
+ <integer name="config_sidefpsPostAuthDowntime">400</integer>
+
+ <!-- The time (in millis) that a finger tap will wait for a power button
+ before dismissing the power dialog during enrollment(for side fingerprint) -->
+ <integer name="config_sidefpsEnrollPowerPressWindow">300</integer>
+
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
It declares the package name of VoiceInteractionService that should be started. -->
<string translatable="false" name="config_forceVoiceInteractionServicePackage"></string>
@@ -4374,6 +4390,9 @@
<!-- Name of a font family to use for medium body text. -->
<string name="config_bodyFontFamilyMedium" translatable="false">sans-serif-medium</string>
+ <!-- Name of the font family to use in the default lockscreen clock -->
+ <string name="config_clockFontFamily" translatable="false">monospace</string>
+
<!-- Size of icon shown beside a preference locked by admin -->
<dimen name="config_restrictedIconSize">@dimen/restricted_icon_size_material</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c6b60f5..d528385 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1751,6 +1751,8 @@
<string name="fingerprint_error_security_update_required">Sensor temporarily disabled.</string>
<!-- Generic error message shown when fingerprint needs calibration [CHAR LIMIT=150] -->
<string name="fingerprint_error_bad_calibration">Can\u2019t use fingerprint sensor. Visit a repair provider</string>
+ <!-- Generic error message shown when the power button has been pressed. [CHAR LIMIT=150] -->
+ <string name="fingerprint_error_power_pressed">Power button pressed</string>
<!-- Template to be used to name enrolled fingerprints by default. -->
<string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string>
@@ -1838,9 +1840,9 @@
<!-- Message shown during acquisition when the sensor is dirty [CHAR LIMIT=100] -->
<string name="face_acquired_sensor_dirty">Clean the top of your screen, including the black bar</string>
<!-- Message shown during acquisition when dark glasses were detected [CHAR LIMIT=75] -->
- <string name="face_acquired_dark_glasses_detected">Your face must be fully visible</string>
+ <string name="face_acquired_dark_glasses_detected">@string/face_acquired_dark_glasses_detected_alt</string>
<!-- Message shown during acquisition when a mouth covering was detected [CHAR LIMIT=75] -->
- <string name="face_acquired_mouth_covering_detected">Your face must be fully visible</string>
+ <string name="face_acquired_mouth_covering_detected">@string/face_acquired_mouth_covering_detected_alt</string>
<!-- Message shown during face acquisition when the sensor needs to be recalibrated [CHAR LIMIT=75] -->
<string name="face_acquired_recalibrate_alt">Can\u2019t create your face model. Try again.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1438e7f..b2fd28f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1574,6 +1574,7 @@
<java-symbol type="layout" name="time_picker_dialog" />
<java-symbol type="layout" name="tooltip" />
<java-symbol type="layout" name="transient_notification" />
+ <java-symbol type="layout" name="transient_notification_with_icon" />
<java-symbol type="layout" name="voice_interaction_session" />
<java-symbol type="layout" name="web_text_view_dropdown" />
<java-symbol type="layout" name="webview_find" />
@@ -2621,10 +2622,15 @@
<java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
+ <java-symbol type="string" name="fingerprint_error_power_pressed" />
<!-- Fingerprint config -->
<java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
<java-symbol type="bool" name="config_fingerprintSupportsGestures"/>
+ <java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/>
+ <java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/>
+ <java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/>
+ <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/>
<!-- Face authentication messages -->
<java-symbol type="string" name="face_recalibrate_notification_name" />
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 7338c3a..ddc05e0 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -204,6 +204,19 @@
assertEquals(100, Color.alpha(bitmap.getPixel(50, 50)));
}
+ @Test
+ public void testSetBounds() throws Exception {
+ mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
+ mIconDrawable.setBounds(0, 0, 100, 100);
+
+ assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds());
+ assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds());
+
+ mIconDrawable.setBounds(10, 10, 110, 110);
+ assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds());
+ assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds());
+ }
+
//
// Utils
//
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 34a1fd8..44bb062 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -92,7 +92,7 @@
@Test
public void testImeVisibility() {
final InsetsSourceControl ime =
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE);
+ new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -121,7 +121,7 @@
// set control and verify visibility is applied.
InsetsSourceControl control =
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE);
+ new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
@@ -158,7 +158,7 @@
// set control and verify visibility is applied.
InsetsSourceControl control = Mockito.spy(
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE));
+ new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE));
// Simulate IME source control set this flag when the target has starting window.
control.setSkipAnimationOnce(true);
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 4f1da1b2..d0f7fe04 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -95,12 +95,13 @@
() -> mMockTransaction, mMockController);
topConsumer.setControl(
new InsetsSourceControl(
- ITYPE_STATUS_BAR, mTopLeash, new Point(0, 0), Insets.of(0, 100, 0, 0)),
+ ITYPE_STATUS_BAR, mTopLeash, true, new Point(0, 0),
+ Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
mInsetsState, () -> mMockTransaction, mMockController);
- navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash,
+ navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash, true,
new Point(400, 0), Insets.of(0, 0, 100, 0)), new int[1], new int[1]);
navConsumer.hide();
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index dcb1835..ed6a649 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -223,7 +223,7 @@
InsetsSourceControl control =
new InsetsSourceControl(
- ITYPE_STATUS_BAR, mLeash, new Point(), Insets.of(0, 10, 0, 0));
+ ITYPE_STATUS_BAR, mLeash, true, new Point(), Insets.of(0, 10, 0, 0));
mController.onControlsChanged(new InsetsSourceControl[]{control});
mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
new LinearInterpolator(),
@@ -926,7 +926,8 @@
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(type, copy, new Point(), Insets.NONE);
+ return new InsetsSourceControl(type, copy, InsetsState.getDefaultVisibility(type),
+ new Point(), Insets.NONE);
}
private InsetsSourceControl[] createSingletonControl(@InternalInsetsType int type) {
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index b3aa7e8..2054b4f 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -110,7 +110,8 @@
instrumentation.waitForIdleSync();
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
+ new Point(), Insets.NONE),
new int[1], new int[1]);
}
@@ -180,7 +181,8 @@
verifyZeroInteractions(mMockTransaction);
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
+ new Point(), Insets.NONE),
new int[1], hideTypes);
assertEquals(statusBars(), hideTypes[0]);
assertFalse(mRemoveSurfaceCalled);
@@ -191,14 +193,14 @@
public void testRestore_noAnimation() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mConsumer.hide();
- mController.onStateChanged(mState);
mConsumer.setControl(null, new int[1], new int[1]);
reset(mMockTransaction);
verifyZeroInteractions(mMockTransaction);
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, false /* initialVisible */,
+ new Point(), Insets.NONE),
new int[1], hideTypes);
assertTrue(mRemoveSurfaceCalled);
assertEquals(0, hideTypes[0]);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f01e2e8..c6d9eba 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3139,6 +3139,12 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
+ "956467125": {
+ "message": "Reparenting Activity to embedded TaskFragment, but the Activity is not collected",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+ },
"958338552": {
"message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 2f56b18..6939a72 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -26,8 +26,6 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -39,8 +37,6 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.PathParser;
@@ -106,7 +102,8 @@
private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
/**
- * Clip path defined in R.string.config_icon_mask.
+ * Unused path.
+ * TODO: Remove once the layoutLib is updated
*/
private static Path sMask;
@@ -114,9 +111,10 @@
* Scaled mask based on the view bounds.
*/
private final Path mMask;
- private final Path mMaskScaleOnly;
+ private final Path mMaskTransformed;
private final Matrix mMaskMatrix;
private final Region mTransparentRegion;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
* Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
@@ -131,19 +129,10 @@
*/
LayerState mLayerState;
- private Shader mLayersShader;
- private Bitmap mLayersBitmap;
-
private final Rect mTmpOutRect = new Rect();
private Rect mHotspotBounds;
private boolean mMutated;
- private boolean mSuspendChildInvalidation;
- private boolean mChildRequestedInvalidation;
- private final Canvas mCanvas;
- private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
- Paint.FILTER_BITMAP_FLAG);
-
/**
* Constructor used for xml inflation.
*/
@@ -157,19 +146,16 @@
*/
AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
- // config_icon_mask from context bound resource may have been chaged using
+ // config_icon_mask from context bound resource may have been changed using
// OverlayManager. Read that one first.
Resources r = ActivityThread.currentActivityThread() == null
? Resources.getSystem()
: ActivityThread.currentActivityThread().getApplication().getResources();
- // TODO: either make sMask update only when config_icon_mask changes OR
- // get rid of it all-together in layoutlib
- sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
- mMask = new Path(sMask);
- mMaskScaleOnly = new Path(mMask);
+ mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
+ mMaskTransformed = new Path();
mMaskMatrix = new Matrix();
- mCanvas = new Canvas();
mTransparentRegion = new Region();
+ mPaint.setColor(Color.BLACK);
}
private ChildDrawable createChildDrawable(Drawable drawable) {
@@ -280,7 +266,7 @@
* @return the mask path object used to clip the drawable
*/
public Path getIconMask() {
- return mMask;
+ return mMaskTransformed;
}
/**
@@ -322,92 +308,47 @@
if (bounds.isEmpty()) {
return;
}
- updateLayerBounds(bounds);
- }
-
- private void updateLayerBounds(Rect bounds) {
- if (bounds.isEmpty()) {
- return;
- }
- try {
- suspendChildInvalidation();
- updateLayerBoundsInternal(bounds);
- updateMaskBoundsInternal(bounds);
- } finally {
- resumeChildInvalidation();
- }
- }
-
- /**
- * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
- */
- private void updateLayerBoundsInternal(Rect bounds) {
- int cX = bounds.width() / 2;
- int cY = bounds.height() / 2;
+ // Set the child layer bounds bigger than the view port size
+ // by {@link #DEFAULT_VIEW_PORT_SCALE}
+ float cX = bounds.exactCenterX();
+ float cY = bounds.exactCenterY();
+ float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2);
+ float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2);
+ final Rect outRect = mTmpOutRect;
+ outRect.set(
+ (int) (cX - insetWidth),
+ (int) (cY - insetHeight),
+ (int) (cX + insetWidth),
+ (int) (cY + insetHeight));
for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
final ChildDrawable r = mLayerState.mChildren[i];
- final Drawable d = r.mDrawable;
- if (d == null) {
- continue;
+ if (r.mDrawable != null) {
+ r.mDrawable.setBounds(outRect);
}
-
- int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
- int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
- final Rect outRect = mTmpOutRect;
- outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
-
- d.setBounds(outRect);
- }
- }
-
- private void updateMaskBoundsInternal(Rect b) {
- // reset everything that depends on the view bounds
- mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
- sMask.transform(mMaskMatrix, mMaskScaleOnly);
-
- mMaskMatrix.postTranslate(b.left, b.top);
- sMask.transform(mMaskMatrix, mMask);
-
- if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
- || mLayersBitmap.getHeight() != b.height()) {
- mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
}
- mPaint.setShader(null);
+ // Update the clipping mask
+ mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
+ mMaskMatrix.postTranslate(bounds.left, bounds.top);
+ mMask.transform(mMaskMatrix, mMaskTransformed);
+
+ // Clear the transparent region, it is calculated lazily
mTransparentRegion.setEmpty();
- mLayersShader = null;
}
@Override
public void draw(Canvas canvas) {
- if (mLayersBitmap == null) {
- return;
+ int saveCount = canvas.save();
+ canvas.clipPath(mMaskTransformed);
+ canvas.drawPaint(mPaint);
+ if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
+ mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas);
}
- if (mLayersShader == null) {
- mCanvas.setBitmap(mLayersBitmap);
- mCanvas.drawColor(Color.BLACK);
- if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
- mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas);
- }
- if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
- mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas);
- }
- mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
- mPaint.setShader(mLayersShader);
+ if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
+ mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas);
}
- if (mMaskScaleOnly != null) {
- Rect bounds = getBounds();
- canvas.translate(bounds.left, bounds.top);
- canvas.drawPath(mMaskScaleOnly, mPaint);
- canvas.translate(-bounds.left, -bounds.top);
- }
- }
-
- @Override
- public void invalidateSelf() {
- mLayersShader = null;
- super.invalidateSelf();
+ canvas.restoreToCount(saveCount);
}
@Override
@@ -600,37 +541,9 @@
return false;
}
- /**
- * Temporarily suspends child invalidation.
- *
- * @see #resumeChildInvalidation()
- */
- private void suspendChildInvalidation() {
- mSuspendChildInvalidation = true;
- }
-
- /**
- * Resumes child invalidation after suspension, immediately performing an
- * invalidation if one was requested by a child during suspension.
- *
- * @see #suspendChildInvalidation()
- */
- private void resumeChildInvalidation() {
- mSuspendChildInvalidation = false;
-
- if (mChildRequestedInvalidation) {
- mChildRequestedInvalidation = false;
- invalidateSelf();
- }
- }
-
@Override
public void invalidateDrawable(@NonNull Drawable who) {
- if (mSuspendChildInvalidation) {
- mChildRequestedInvalidation = true;
- } else {
- invalidateSelf();
- }
+ invalidateSelf();
}
@Override
@@ -714,6 +627,13 @@
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setAlpha(alpha);
+ }
+ }
}
@Override
@@ -816,10 +736,6 @@
}
}
- if (changed) {
- updateLayerBounds(getBounds());
- }
-
return changed;
}
@@ -835,10 +751,6 @@
}
}
- if (changed) {
- updateLayerBounds(getBounds());
- }
-
return changed;
}
@@ -979,6 +891,7 @@
int mDensity;
// The density to use when inflating/looking up the children drawables. A value of 0 means
+
// use the system's density.
int mSrcDensityOverride = 0;
diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java
index e56bd51..789ff06 100644
--- a/identity/java/android/security/identity/Util.java
+++ b/identity/java/android/security/identity/Util.java
@@ -20,12 +20,12 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
-import java.util.Collection;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -36,15 +36,6 @@
public class Util {
private static final String TAG = "Util";
- static int[] integerCollectionToArray(Collection<Integer> collection) {
- int[] result = new int[collection.size()];
- int n = 0;
- for (int item : collection) {
- result[n++] = item;
- }
- return result;
- }
-
static byte[] stripLeadingZeroes(byte[] value) {
int n = 0;
while (n < value.length && value[n] == 0) {
@@ -61,15 +52,47 @@
static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) {
ECPoint w = ((ECPublicKey) publicKey).getW();
- // X and Y are always positive so for interop we remove any leading zeroes
- // inserted by the BigInteger encoder.
- byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
- byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
+ BigInteger x = w.getAffineX();
+ BigInteger y = w.getAffineY();
+ if (x.compareTo(BigInteger.ZERO) < 0) {
+ throw new RuntimeException("X is negative");
+ }
+ if (y.compareTo(BigInteger.ZERO) < 0) {
+ throw new RuntimeException("Y is negative");
+ }
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(0x04);
- baos.write(x);
- baos.write(y);
+
+ // Each coordinate may be encoded in 33*, 32, or fewer bytes.
+ //
+ // * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
+ // minimum number of bytes required to represent this BigInteger, including at
+ // least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
+ // the MSB is always 0x00. This is taken care of by calling calling
+ // stripLeadingZeroes().
+ //
+ // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
+ // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
+ // where X and Y are encoded in exactly 32 byte, big endian integer values each.
+ //
+ byte[] xBytes = stripLeadingZeroes(x.toByteArray());
+ if (xBytes.length > 32) {
+ throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
+ }
+ for (int n = 0; n < 32 - xBytes.length; n++) {
+ baos.write(0x00);
+ }
+ baos.write(xBytes);
+
+ byte[] yBytes = stripLeadingZeroes(y.toByteArray());
+ if (yBytes.length > 32) {
+ throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
+ }
+ for (int n = 0; n < 32 - yBytes.length; n++) {
+ baos.write(0x00);
+ }
+ baos.write(yBytes);
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Unexpected IOException", e);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 3ff5315..0dba6b0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -72,6 +72,7 @@
@NonNull Configuration parentConfig);
void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken);
+ void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
}
/**
@@ -323,4 +324,18 @@
mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
}
}
+
+ @Override
+ public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
+ @Nullable TaskFragmentInfo taskFragmentInfo,
+ int opType, @NonNull Throwable exception) {
+ if (taskFragmentInfo != null) {
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ mFragmentInfos.put(fragmentToken, taskFragmentInfo);
+ }
+
+ if (mCallback != null) {
+ mCallback.onTaskFragmentError(taskFragmentInfo, opType);
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index da9fd0c2..c688080 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -19,6 +19,8 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -296,6 +298,37 @@
}
}
+ @Override
+ public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
+ synchronized (mLock) {
+ switch (opType) {
+ case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
+ final TaskFragmentContainer container;
+ if (taskFragmentInfo != null) {
+ container = getContainer(taskFragmentInfo.getFragmentToken());
+ } else {
+ container = null;
+ }
+ if (container == null) {
+ break;
+ }
+
+ // Update the latest taskFragmentInfo and perform necessary clean-up
+ container.setInfo(taskFragmentInfo);
+ container.clearPendingAppearedActivities();
+ if (container.isEmpty()) {
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ }
+ break;
+ }
+ default:
+ Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo
+ + ", opType = " + opType);
+ }
+ }
+ }
+
/** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index a188e2b..37f5b6d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -193,6 +193,11 @@
mPendingAppearedActivities.remove(pendingAppearedActivity);
}
+ void clearPendingAppearedActivities() {
+ mPendingAppearedActivities.clear();
+ mPendingAppearedIntent = null;
+ }
+
@Nullable
Intent getPendingAppearedIntent() {
return mPendingAppearedIntent;
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 f85f9d6..5dd5149 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
@@ -411,8 +411,8 @@
@WMSingleton
@Provides
- static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper() {
- return new PipSurfaceTransactionHelper();
+ static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
+ return new PipSurfaceTransactionHelper(context);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index cc741d3..35a309a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -27,6 +27,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Trace;
+import android.view.Choreographer;
import androidx.annotation.Nullable;
@@ -144,6 +145,25 @@
}
/**
+ * Provide a Shell main-thread {@link Choreographer} with the app vsync.
+ *
+ * @param executor the executor of the shell main thread
+ */
+ @WMSingleton
+ @Provides
+ @ShellMainThread
+ public static Choreographer provideShellMainChoreographer(
+ @ShellMainThread ShellExecutor executor) {
+ try {
+ final Choreographer[] choreographer = new Choreographer[1];
+ executor.executeBlocking(() -> choreographer[0] = Choreographer.getInstance());
+ return choreographer[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to obtain main Choreographer.", e);
+ }
+ }
+
+ /**
* Provide a Shell animation-thread Executor.
*/
@WMSingleton
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 0f33f4c..0dc293b 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
@@ -20,6 +20,7 @@
import android.content.pm.LauncherApps;
import android.os.Handler;
import android.os.UserManager;
+import android.view.Choreographer;
import android.view.WindowManager;
import com.android.internal.jank.InteractionJankMonitor;
@@ -173,12 +174,14 @@
static WindowDecorViewModel<?> provideWindowDecorViewModel(
Context context,
@ShellMainThread Handler mainHandler,
+ @ShellMainThread Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue) {
return new CaptionWindowDecorViewModel(
context,
mainHandler,
+ mainChoreographer,
taskOrganizer,
displayController,
syncQueue);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index 9478b34..f376e1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -64,8 +64,8 @@
@VisibleForTesting
final Rect mCurrentDisplayBounds = new Rect();
// The default display cutout in natural orientation.
- private Insets mDefaultCutoutInsets;
- private Insets mCurrentCutoutInsets;
+ private Insets mDefaultCutoutInsets = Insets.NONE;
+ private Insets mCurrentCutoutInsets = Insets.NONE;
private boolean mIsDefaultPortrait;
private int mStatusBarHeight;
@VisibleForTesting
@@ -78,27 +78,35 @@
private final DisplayController.OnDisplaysChangedListener mListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
+ public void onDisplayAdded(int displayId) {
+ onDisplayChanged(displayId);
+ }
+
+ @Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
- DisplayLayout displayLayout =
- mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
- if (displayLayout == null) {
- return;
- }
- final boolean rotationChanged = mRotation != displayLayout.rotation();
- mRotation = displayLayout.rotation();
- if (rotationChanged || isDisplayBoundsChanged()) {
- updateBoundsAndOffsets(true /* enabled */);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- applyAllBoundsAndOffsets(wct, t);
- applyTransaction(wct, t);
- }
+ onDisplayChanged(displayId);
}
};
+ private void onDisplayChanged(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
+ if (displayLayout == null) {
+ return;
+ }
+ final boolean rotationChanged = mRotation != displayLayout.rotation();
+ mRotation = displayLayout.rotation();
+ if (rotationChanged || isDisplayBoundsChanged()) {
+ updateBoundsAndOffsets(true /* enabled */);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ applyAllBoundsAndOffsets(wct, t);
+ applyTransaction(wct, t);
+ }
+ }
+
HideDisplayCutoutOrganizer(Context context, DisplayController displayController,
ShellExecutor mainExecutor) {
super(mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index c0bc108..3ac08a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -39,6 +39,10 @@
private int mCornerRadius;
private int mShadowRadius;
+ public PipSurfaceTransactionHelper(Context context) {
+ onDensityOrFontScaleChanged(context);
+ }
+
/**
* Called when display size or font size of settings changed
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index da88c2d..1155ea1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,6 +62,7 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -444,7 +445,7 @@
// When exit to fullscreen with Shell transition enabled, we update the Task windowing
// mode directly so that it can also trigger display rotation and visibility update in
// the same transition if there will be any.
- wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mToken, getOutPipWindowingMode());
// We can inherit the parent bounds as it is going to be fullscreen. The
// destinationBounds calculated above will be incorrect if this is with rotation.
wct.setBounds(mToken, null);
@@ -543,7 +544,7 @@
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mToken, null);
- wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mToken, getOutPipWindowingMode());
wct.reorder(mToken, false);
mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
null /* destinationBounds */);
@@ -930,6 +931,12 @@
/** Called when exiting PIP transition is finished to do the state cleanup. */
void onExitPipFinished(TaskInfo info) {
+ if (mLeash == null) {
+ // TODO(239461594): Remove once the double call to onExitPipFinished() is fixed
+ Log.w(TAG, "Warning, onExitPipFinished() called multiple times in the same sessino");
+ return;
+ }
+
clearWaitForFixedRotation();
if (mSwipePipToHomeOverlay != null) {
removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */);
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 53ec39d..7fb961f 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
@@ -352,41 +352,31 @@
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
+ if (fillInIntent == null) {
+ fillInIntent = new Intent();
+ }
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the
+ // current top activity since it's going to be put into another side of the split. This
+ // prevents the current top activity from going into pip mode due to user leaving event.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
+ // split.
+ if (isLaunchingAdjacently(intent.getIntent(), position)) {
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ }
+
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
- try {
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
-
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
- fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
-
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split.
- if (isLaunchingAdjacently(intent.getIntent(), position)) {
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
- }
-
- intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */,
- null /* requiredPermission */, options);
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "Failed to launch task", e);
- }
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
- private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent,
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position);
-
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
mStageCoordinator.prepareEvictChildTasks(position, evictWct);
@@ -397,8 +387,8 @@
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
if (apps == null || apps.length == 0) {
- if (startSameActivityAdjacently) {
- // Switch split position if dragging the same activity to another side.
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
setSideStagePosition(SplitLayout.reversePosition(
mStageCoordinator.getSideStagePosition()));
}
@@ -408,8 +398,6 @@
return;
}
- mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
- false /* applyResizingOffset */);
for (int i = 0; i < apps.length; ++i) {
if (apps[i].mode == MODE_OPENING) {
t.show(apps[i].leash);
@@ -432,18 +420,6 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
- // Flag this as a no-user-action launch to prevent sending user leaving event to the current
- // top activity since it's going to be put into another side of the split. This prevents the
- // current top activity from going into pip mode due to user leaving event.
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- if (startSameActivityAdjacently) {
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
- }
-
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 056cd58..83bdf8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -62,13 +62,12 @@
private final Runnable mOnFinish;
DismissTransition mPendingDismiss = null;
- IBinder mPendingEnter = null;
- IBinder mPendingRecent = null;
+ TransitSession mPendingEnter = null;
+ TransitSession mPendingRecent = null;
private IBinder mAnimatingTransition = null;
OneShotRemoteHandler mPendingRemoteHandler = null;
private OneShotRemoteHandler mActiveRemoteHandler = null;
- private boolean mEnterTransitionMerged;
private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
@@ -145,7 +144,7 @@
continue;
}
- if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer())
|| sideRoot.equals(change.getContainer()))) {
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
@@ -171,12 +170,40 @@
onFinish(null /* wct */, null /* wctCB */);
}
+ boolean isPendingTransition(IBinder transition) {
+ return isPendingEnter(transition)
+ || isPendingDismiss(transition)
+ || isPendingRecent(transition);
+ }
+
+ boolean isPendingEnter(IBinder transition) {
+ return mPendingEnter != null && mPendingEnter.mTransition == transition;
+ }
+
+ boolean isPendingRecent(IBinder transition) {
+ return mPendingRecent != null && mPendingRecent.mTransition == transition;
+ }
+
+ boolean isPendingDismiss(IBinder transition) {
+ return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
- IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
- @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
- @NonNull Transitions.TransitionHandler handler) {
+ IBinder startEnterTransition(
+ @WindowManager.TransitionType int transitType,
+ WindowContainerTransaction wct,
+ @Nullable RemoteTransition remoteTransition,
+ Transitions.TransitionHandler handler,
+ @Nullable TransitionCallback callback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- mPendingEnter = transition;
+ setEnterTransition(transition, remoteTransition, callback);
+ return transition;
+ }
+
+ /** Sets a transition to enter split. */
+ void setEnterTransition(@NonNull IBinder transition,
+ @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
+ mPendingEnter = new TransitSession(transition, callback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -184,7 +211,9 @@
mTransitions.getMainExecutor(), remoteTransition);
mPendingRemoteHandler.setTransition(transition);
}
- return transition;
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Enter split screen");
}
/** Starts a transition to dismiss split. */
@@ -209,8 +238,8 @@
}
void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition) {
- mPendingRecent = transition;
+ @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
+ mPendingRecent = new TransitSession(transition, callback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -226,6 +255,18 @@
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
+
+ if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
+ mPendingRecent.mCallback = new TransitionCallback() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ }
+ };
+ }
+
if (mActiveRemoteHandler != null) {
mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
} else {
@@ -247,38 +288,55 @@
}
void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
- if (aborted) return;
+ if (isPendingEnter(transition)) {
+ if (!aborted) {
+ // An enter transition got merged, appends the rest operations to finish entering
+ // split screen.
+ // TODO (b/238856352): Passed-in the proper finish transition to merge instead.
+ if (mFinishTransaction == null) {
+ mFinishTransaction = mTransactionPool.acquire();
+ }
+ mStageCoordinator.finishEnterSplitScreen(mFinishTransaction);
+ }
- // Once a pending enter transition got merged, make sure to append the reset of finishing
- // operations to the finish transition.
- if (transition == mPendingEnter) {
- mFinishTransaction = mTransactionPool.acquire();
- mStageCoordinator.finishEnterSplitScreen(mFinishTransaction);
+ mPendingEnter.mCallback.onTransitionConsumed(aborted);
mPendingEnter = null;
mPendingRemoteHandler = null;
- mEnterTransitionMerged = true;
+ } else if (isPendingDismiss(transition)) {
+ mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss = null;
+ } else if (isPendingRecent(transition)) {
+ mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent = null;
+ mPendingRemoteHandler = null;
}
}
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- if (mAnimatingTransition == mPendingEnter) {
+
+ TransitionCallback callback = null;
+ if (isPendingEnter(mAnimatingTransition)) {
+ callback = mPendingEnter.mCallback;
mPendingEnter = null;
}
- if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) {
+ if (isPendingDismiss(mAnimatingTransition)) {
+ callback = mPendingDismiss.mCallback;
mPendingDismiss = null;
}
- if (mAnimatingTransition == mPendingRecent) {
- if (!mEnterTransitionMerged) {
- if (wct == null) wct = new WindowContainerTransaction();
- mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction);
- }
+ if (isPendingRecent(mAnimatingTransition)) {
+ callback = mPendingRecent.mCallback;
mPendingRecent = null;
}
+
+ if (callback != null) {
+ if (wct == null) wct = new WindowContainerTransaction();
+ callback.onTransitionFinished(wct, mFinishTransaction);
+ }
+
mPendingRemoteHandler = null;
mActiveRemoteHandler = null;
mAnimatingTransition = null;
- mEnterTransitionMerged = false;
mOnFinish.run();
if (mFinishTransaction != null) {
@@ -382,17 +440,34 @@
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
+ /** Clean-up callbacks for transition. */
+ interface TransitionCallback {
+ /** Calls when the transition got consumed. */
+ default void onTransitionConsumed(boolean aborted) {}
+
+ /** Calls when the transition finished. */
+ default void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {}
+ }
+
+ /** Session for a transition and its clean-up callback. */
+ static class TransitSession {
+ final IBinder mTransition;
+ TransitionCallback mCallback;
+
+ TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ mTransition = transition;
+ mCallback = callback != null ? callback : new TransitionCallback() {};
+ }
+ }
+
/** Bundled information of dismiss transition. */
- static class DismissTransition {
- IBinder mTransition;
-
- int mReason;
-
- @SplitScreen.StageType
- int mDismissTop;
+ static class DismissTransition extends TransitSession {
+ final int mReason;
+ final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- this.mTransition = transition;
+ super(transition, null /* callback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
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 2229e26..3c7db33 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
@@ -24,6 +24,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -214,6 +215,33 @@
}
};
+ private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
+ new SplitScreenTransitions.TransitionCallback() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current split, so we
+ // can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
+
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+ };
+
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
@@ -337,15 +365,23 @@
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
targetStage.evictAllChildren(evictWct);
targetStage.addTask(task, wct);
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
- }
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE,
- wct, null, this);
+ mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
+ null, this, new SplitScreenTransitions.TransitionCallback() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
+ }
+ }
+ });
} else {
+ if (!evictWct.isEmpty()) {
+ wct.merge(evictWct, true /* transfer */);
+ }
mTaskOrganizer.applyTransaction(wct);
}
return true;
@@ -365,6 +401,39 @@
return result;
}
+ /** Launches an activity into split. */
+ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictChildTasks(position, evictWct);
+
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position);
+
+ mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
+ new SplitScreenTransitions.TransitionCallback() {
+ @Override
+ public void onTransitionConsumed(boolean aborted) {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if (aborted
+ && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePositionAnimated(
+ SplitLayout.reversePosition(mSideStagePosition));
+ }
+ }
+
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
+ }
+ }
+ });
+ }
+
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
@@ -395,7 +464,7 @@
wct.startTask(sideTaskId, sideOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
}
/** Starts 2 tasks in one legacy transition. */
@@ -617,11 +686,13 @@
}
int getTaskId(@SplitPosition int splitPosition) {
- if (mSideStagePosition == splitPosition) {
- return mSideStage.getTopVisibleChildTaskId();
- } else {
- return mMainStage.getTopVisibleChildTaskId();
+ if (splitPosition == SPLIT_POSITION_UNDEFINED) {
+ return INVALID_TASK_ID;
}
+
+ return mSideStagePosition == splitPosition
+ ? mSideStage.getTopVisibleChildTaskId()
+ : mMainStage.getTopVisibleChildTaskId();
}
void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
@@ -861,6 +932,7 @@
mSplitLayout.init();
setDividerVisibility(true, t);
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ t.show(mRootTaskLeash);
setSplitsVisible(true);
mShouldUpdateRecents = true;
updateRecentTasksSplitPair();
@@ -1211,7 +1283,7 @@
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
- if (!hasChildren && !mIsExiting) {
+ if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
if (isSideStage && mMainStageListener.mVisible) {
// Exit to main stage if side stage no longer has children.
if (ENABLE_SHELL_TRANSITIONS) {
@@ -1231,7 +1303,7 @@
EXIT_REASON_APP_FINISHED);
}
}
- } else if (isSideStage && !mMainStage.isActive()) {
+ } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.removeAllTasks(wct, true);
@@ -1543,14 +1615,14 @@
} else if (activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
- mSplitTransitions.setRecentTransition(transition,
- request.getRemoteTransition());
+ mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
+ mRecentTransitionCallback);
} else if (mSplitTransitions.mPendingRecent == null) {
// If split-task is not controlled by recents animation
// and occluded by the other fullscreen task, dismiss both.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
- mSplitTransitions.setDismissTransition(transition,
- STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
+ mSplitTransitions.setDismissTransition(
+ transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
}
}
} else {
@@ -1558,7 +1630,8 @@
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.mPendingEnter = transition;
+ mSplitTransitions.setEnterTransition(
+ transition, request.getRemoteTransition(), null /* callback */);
}
}
return out;
@@ -1614,10 +1687,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mSplitTransitions.mPendingEnter
- && transition != mSplitTransitions.mPendingRecent
- && (mSplitTransitions.mPendingDismiss == null
- || mSplitTransitions.mPendingDismiss.mTransition != transition)) {
+ if (!mSplitTransitions.isPendingTransition(transition)) {
// Not entering or exiting, so just do some house-keeping and validation.
// If we're not in split-mode, just abort so something else can handle it.
@@ -1664,12 +1734,11 @@
}
boolean shouldAnimate = true;
- if (mSplitTransitions.mPendingEnter == transition) {
+ if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingRecent == transition) {
+ } else if (mSplitTransitions.isPendingRecent(transition)) {
shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingDismiss != null
- && mSplitTransitions.mPendingDismiss.mTransition == transition) {
+ } else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
}
@@ -1837,28 +1906,6 @@
return true;
}
- void onRecentTransitionFinished(WindowContainerTransaction wct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current split so we can
- // restore the divider bar.
- for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- setDividerVisibility(true, finishT);
- return;
- }
- }
-
- // Dismiss the split screen is it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
-
private void addDividerBarToTransition(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index dcd6277..0bec543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -483,11 +483,11 @@
postStartTransactionCallbacks.add(t ->
startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
mTransactionPool, mMainExecutor, mAnimExecutor,
- null /* position */, cornerRadius, clipRect));
+ change.getEndRelOffset(), cornerRadius, clipRect));
} else {
startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
- cornerRadius, clipRect);
+ mTransactionPool, mMainExecutor, mAnimExecutor,
+ change.getEndRelOffset(), cornerRadius, clipRect);
}
if (info.getAnimationOptions() != null) {
@@ -934,7 +934,7 @@
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top),
+ mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
cornerRadius, change.getEndAbsBounds());
}
@@ -959,7 +959,7 @@
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, null /* position */,
+ mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
cornerRadius, change.getEndAbsBounds());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 46f73fd..1005ef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -34,7 +34,6 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -85,8 +84,6 @@
private final Context mContext;
private final TransactionPool mTransactionPool;
private final float[] mTmpFloats = new float[9];
- // Complete transformations being applied.
- private final Matrix mSnapshotInitialMatrix = new Matrix();
/** The leash of display. */
private final SurfaceControl mSurfaceControl;
private final Rect mStartBounds = new Rect();
@@ -169,16 +166,15 @@
.setName("RotationLayer")
.build();
- GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
- screenshotBuffer.getHardwareBuffer());
-
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.setPosition(mAnimLeash, 0, 0);
t.setAlpha(mAnimLeash, 1);
t.show(mAnimLeash);
- t.setBuffer(mScreenshotLayer, buffer);
- t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
+ final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
+ final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+ t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace());
+ t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
@@ -189,8 +185,7 @@
.setName("BackColorSurface")
.build();
- HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
+ mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
@@ -202,7 +197,7 @@
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
- setRotation(t);
+ setScreenshotTransform(t);
t.apply();
}
@@ -210,19 +205,36 @@
return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
}
- private void setRotation(SurfaceControl.Transaction t) {
- // Compute the transformation matrix that must be applied
- // to the snapshot to make it stay in the same original position
- // with the current screen rotation.
- int delta = deltaRotation(mEndRotation, mStartRotation);
- createRotationMatrix(delta, mStartWidth, mStartHeight, mSnapshotInitialMatrix);
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
-
- private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
+ private void setScreenshotTransform(SurfaceControl.Transaction t) {
if (mScreenshotLayer == null) {
return;
}
+ final Matrix matrix = new Matrix();
+ final int delta = deltaRotation(mEndRotation, mStartRotation);
+ if (delta != 0) {
+ // Compute the transformation matrix that must be applied to the snapshot to make it
+ // stay in the same original position with the current screen rotation.
+ switch (delta) {
+ case Surface.ROTATION_90:
+ matrix.setRotate(90, 0, 0);
+ matrix.postTranslate(mStartHeight, 0);
+ break;
+ case Surface.ROTATION_180:
+ matrix.setRotate(180, 0, 0);
+ matrix.postTranslate(mStartWidth, mStartHeight);
+ break;
+ case Surface.ROTATION_270:
+ matrix.setRotate(270, 0, 0);
+ matrix.postTranslate(0, mStartWidth);
+ break;
+ }
+ } else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
+ && (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
+ // Display resizes without rotation change.
+ final float scale = Math.max((float) mEndWidth / mStartHeight,
+ (float) mEndHeight / mStartHeight);
+ matrix.setScale(scale, scale);
+ }
matrix.getValues(mTmpFloats);
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
@@ -230,9 +242,7 @@
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
t.setAlpha(mScreenshotLayer, (float) 1.0);
- t.show(mScreenshotLayer);
}
/**
@@ -486,27 +496,6 @@
return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
}
- private static void createRotationMatrix(int rotation, int width, int height,
- Matrix outMatrix) {
- switch (rotation) {
- case Surface.ROTATION_0:
- outMatrix.reset();
- break;
- case Surface.ROTATION_90:
- outMatrix.setRotate(90, 0, 0);
- outMatrix.postTranslate(height, 0);
- break;
- case Surface.ROTATION_180:
- outMatrix.setRotate(180, 0, 0);
- outMatrix.postTranslate(width, height);
- break;
- case Surface.ROTATION_270:
- outMatrix.setRotate(270, 0, 0);
- outMatrix.postTranslate(0, width);
- break;
- }
- }
-
private static void applyColor(int startColor, int endColor, float[] rgbFloat,
float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
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 6d28d73..fe3724b 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
@@ -25,6 +25,7 @@
import android.app.ActivityTaskManager;
import android.content.Context;
import android.os.Handler;
+import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -45,17 +46,20 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
private final Handler mMainHandler;
+ private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
+ Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue) {
mContext = context;
mMainHandler = mainHandler;
+ mMainChoreographer = mainChoreographer;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
@@ -72,6 +76,7 @@
taskInfo,
taskSurface,
mMainHandler,
+ mMainChoreographer,
mSyncQueue);
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
CaptionTouchEventListener touchEventListener =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index cdca051..2df7104 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
+import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
import android.window.WindowContainerTransaction;
@@ -59,6 +60,7 @@
RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
private final Handler mHandler;
+ private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
private View.OnClickListener mOnCaptionButtonClickListener;
@@ -77,10 +79,12 @@
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
+ Choreographer choreographer,
SyncTransactionQueue syncQueue) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface);
mHandler = handler;
+ mChoreographer = choreographer;
mSyncQueue = syncQueue;
}
@@ -138,6 +142,7 @@
mDragResizeListener = new DragResizeInputListener(
mContext,
mHandler,
+ mChoreographer,
mDisplay.getDisplayId(),
mDecorationContainerSurface,
mDragResizeCallback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 9869d2e..f512b0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -49,6 +49,7 @@
private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
private final Handler mHandler;
+ private final Choreographer mChoreographer;
private final InputManager mInputManager;
private final int mDisplayId;
@@ -68,11 +69,13 @@
DragResizeInputListener(
Context context,
Handler handler,
+ Choreographer choreographer,
int displayId,
SurfaceControl decorationSurface,
DragResizeCallback callback) {
mInputManager = context.getSystemService(InputManager.class);
mHandler = handler;
+ mChoreographer = choreographer;
mDisplayId = displayId;
mDecorationSurface = decorationSurface;
// Use a fake window as the backing surface is a container layer and we don't want to create
@@ -97,7 +100,8 @@
e.rethrowFromSystemServer();
}
- mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler);
+ mInputEventReceiver = new TaskResizeInputEventReceiver(
+ mInputChannel, mHandler, mChoreographer);
mCallback = callback;
}
@@ -171,13 +175,10 @@
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
- private TaskResizeInputEventReceiver(InputChannel inputChannel, Handler handler) {
+ private TaskResizeInputEventReceiver(
+ InputChannel inputChannel, Handler handler, Choreographer choreographer) {
super(inputChannel, handler.getLooper());
-
- final Choreographer[] choreographer = new Choreographer[1];
- handler.runWithScissors(
- () -> choreographer[0] = Choreographer.getInstance(), 0);
- mChoreographer = choreographer[0];
+ mChoreographer = choreographer;
mConsumeBatchEventRunnable = () -> {
mConsumeBatchEventScheduled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index c19a33a..4855fbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -222,10 +222,10 @@
float shadowRadius = outResult.mDensity * shadowRadiusDp;
int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
- mTmpColor[0] = Color.red(backgroundColorInt);
- mTmpColor[1] = Color.green(backgroundColorInt);
- mTmpColor[2] = Color.blue(backgroundColorInt);
- t.setCrop(mTaskBackgroundSurface, taskBounds)
+ mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+ mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+ mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ t.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
index 4922872..f8b3fb3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
@@ -34,13 +34,12 @@
* given {@link SurfaceControl} when calling {@link SurfaceControl.Builder#build()}.
*
* @param mockSurfaceControl the first {@link SurfaceControl} to return
- * @param mockSurfaceControls following {@link SurfaceControl} to return
* @return the mock of {@link SurfaceControl.Builder}
*/
public static SurfaceControl.Builder createMockSurfaceControlBuilder(
- SurfaceControl mockSurfaceControl, SurfaceControl... mockSurfaceControls) {
+ SurfaceControl mockSurfaceControl) {
final SurfaceControl.Builder mockBuilder = mock(SurfaceControl.Builder.class, RETURNS_SELF);
- doReturn(mockSurfaceControl, (Object[]) mockSurfaceControls)
+ doReturn(mockSurfaceControl)
.when(mockBuilder)
.build();
return mockBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 0972cf2..1636c5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -25,6 +25,7 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,6 +62,12 @@
bubbles.put(1, user1Bubbles)
}
+ @After
+ fun teardown() {
+ // Clean up the any persisted bubbles for the next run
+ repository.persistsToDisk(SparseArray())
+ }
+
@Test
fun testReadWriteOperation() {
// Verify read before write doesn't cause FileNotFoundException
@@ -71,4 +78,4 @@
repository.persistsToDisk(bubbles)
assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk()))
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 587782c..5b691f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -122,7 +122,7 @@
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
- ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0), Insets.NONE)
+ ITYPE_IME, mock(SurfaceControl.class), false, new Point(0, 0), Insets.NONE)
};
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 52d78ca..5880ffb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -37,6 +37,7 @@
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.MockSurfaceControlHelper;
import com.android.wm.shell.ShellTestCase;
@@ -62,19 +63,18 @@
@Mock
private TaskInfo mTaskInfo;
-
@Mock
private PipAnimationController.PipAnimationCallback mPipAnimationCallback;
@Before
public void setUp() throws Exception {
- mPipAnimationController = new PipAnimationController(
- new PipSurfaceTransactionHelper());
+ MockitoAnnotations.initMocks(this);
+ mPipAnimationController = new PipAnimationController(new PipSurfaceTransactionHelper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
mLeash = new SurfaceControl.Builder()
.setContainerLayer()
.setName("FakeLeash")
.build();
- MockitoAnnotations.initMocks(this);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index b351f8f..857f578 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -42,6 +42,7 @@
import android.util.Rational;
import android.util.Size;
import android.view.DisplayInfo;
+import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import com.android.wm.shell.MockSurfaceControlHelper;
@@ -150,7 +151,7 @@
final Rational aspectRatio = new Rational(2, 1);
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(aspectRatio)), null /* leash */);
+ createPipParams(aspectRatio)), mock(SurfaceControl.class));
assertEquals(aspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f);
}
@@ -158,7 +159,7 @@
@Test
public void onTaskAppeared_updatesLastPipComponentName() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)),
- null /* leash */);
+ mock(SurfaceControl.class));
assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName());
}
@@ -169,7 +170,7 @@
mSpiedPipTaskOrganizer.onTaskAppeared(
createTaskInfo(mComponent1, createPipParams(null), minSize),
- null /* leash */);
+ mock(SurfaceControl.class));
assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
}
@@ -179,7 +180,7 @@
final Rational startAspectRatio = new Rational(2, 1);
final Rational newAspectRatio = new Rational(1, 2);
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(startAspectRatio)), null /* leash */);
+ createPipParams(startAspectRatio)), mock(SurfaceControl.class));
// It is in entering transition, should defer onTaskInfoChanged callback in this case.
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1,
@@ -197,7 +198,7 @@
final Rational startAspectRatio = new Rational(2, 1);
final Rational newAspectRatio = new Rational(1, 2);
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(startAspectRatio)), null /* leash */);
+ createPipParams(startAspectRatio)), mock(SurfaceControl.class));
mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1,
@@ -210,7 +211,7 @@
@Test
public void onTaskInfoChanged_inPip_updatesLastPipComponentName() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), null /* leash */);
+ createPipParams(null)), mock(SurfaceControl.class));
mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2,
@@ -222,7 +223,7 @@
@Test
public void onTaskInfoChanged_inPip_updatesOverrideMinSize() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), null /* leash */);
+ createPipParams(null)), mock(SurfaceControl.class));
mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
final Size minSize = new Size(400, 320);
@@ -235,7 +236,7 @@
@Test
public void onTaskVanished_clearsPipBounds() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), null /* leash */);
+ createPipParams(null)), mock(SurfaceControl.class));
mPipBoundsState.setBounds(new Rect(100, 100, 200, 150));
mSpiedPipTaskOrganizer.onTaskVanished(createTaskInfo(mComponent1, createPipParams(null)));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 304ca66..1d038f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -182,7 +182,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator);
+ new RemoteTransition(testRemote), mStageCoordinator, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -422,7 +422,7 @@
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 680034bd..d1b837e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -21,23 +21,32 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.argThat;
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;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
+import android.util.DisplayMetrics;
import android.view.Display;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager.LayoutParams;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -53,6 +62,8 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Supplier;
/**
@@ -66,6 +77,8 @@
public class WindowDecorationTests extends ShellTestCase {
private static final int CAPTION_HEIGHT_DP = 32;
private static final int SHADOW_RADIUS_DP = 5;
+ private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
+ private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
private final Rect mOutsetsDp = new Rect();
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
@@ -84,12 +97,11 @@
@Mock
private WindowContainerTransaction mMockWindowContainerTransaction;
- private SurfaceControl.Builder mMockSurfaceControlBuilder;
+ private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlTransaction;
@Before
public void setUp() {
- mMockSurfaceControlBuilder = createMockSurfaceControlBuilder(mock(SurfaceControl.class));
mMockSurfaceControlTransaction = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
@@ -97,6 +109,128 @@
}
@Test
+ public void testLayoutResultCalculation_invisibleTask() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(false)
+ .build();
+ taskInfo.isFocused = false;
+ // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
+ // 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mOutsetsDp.set(10, 20, 30, 40);
+
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(decorContainerSurfaceBuilder, never()).build();
+ verify(taskBackgroundSurfaceBuilder, never()).build();
+ verify(mMockSurfaceControlViewHostFactory, never())
+ .create(any(), any(), any(), anyBoolean());
+
+ verify(mMockSurfaceControlTransaction).hide(taskSurface);
+
+ assertNull(mRelayoutResult.mRootView);
+ }
+
+ @Test
+ public void testLayoutResultCalculation_visibleFocusedTask() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
+ // 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mOutsetsDp.set(10, 20, 30, 40);
+
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setContainerLayer();
+ verify(mMockSurfaceControlTransaction).setTrustedOverlay(decorContainerSurface, true);
+ verify(mMockSurfaceControlTransaction).setPosition(decorContainerSurface, -20, -40);
+ verify(mMockSurfaceControlTransaction).setWindowCrop(decorContainerSurface, 380, 220);
+
+ verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
+ verify(taskBackgroundSurfaceBuilder).setEffectLayer();
+ verify(mMockSurfaceControlTransaction).setWindowCrop(taskBackgroundSurface, 300, 100);
+ verify(mMockSurfaceControlTransaction)
+ .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlTransaction).setShadowRadius(taskBackgroundSurface, 10);
+
+ verify(mMockSurfaceControlViewHostFactory)
+ .create(any(), eq(defaultDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHost)
+ .setView(same(mMockView),
+ argThat(lp -> lp.height == 64
+ && lp.width == 300
+ && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+ if (ViewRootImpl.CAPTION_ON_SHELL) {
+ verify(mMockView).setTaskFocusState(true);
+ verify(mMockWindowContainerTransaction)
+ .addRectInsetsProvider(taskInfo.token,
+ new Rect(100, 300, 400, 364),
+ new int[] { InsetsState.ITYPE_CAPTION_BAR });
+ }
+
+ verify(mMockSurfaceControlTransaction)
+ .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ verify(mMockSurfaceControlTransaction)
+ .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+ verify(mMockSurfaceControlTransaction)
+ .show(taskSurface);
+
+ assertEquals(380, mRelayoutResult.mWidth);
+ assertEquals(220, mRelayoutResult.mHeight);
+ assertEquals(2, mRelayoutResult.mDensity, 0.f);
+ }
+
+ @Test
public void testNotCrashWhenDisplayAppearsAfterTask() {
doReturn(mock(Display.class)).when(mMockDisplayController)
.getDisplay(Display.DEFAULT_DISPLAY);
@@ -145,10 +279,24 @@
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface, () -> mMockSurfaceControlBuilder,
+ taskInfo, testSurface, new MockSurfaceControlBuilderSupplier(),
mMockSurfaceControlViewHostFactory);
}
+ private class MockSurfaceControlBuilderSupplier implements Supplier<SurfaceControl.Builder> {
+ private int mNumOfCalls = 0;
+
+ @Override
+ public SurfaceControl.Builder get() {
+ final SurfaceControl.Builder builder =
+ mNumOfCalls < mMockSurfaceControlBuilders.size()
+ ? mMockSurfaceControlBuilders.get(mNumOfCalls)
+ : createMockSurfaceControlBuilder(mock(SurfaceControl.class));
+ ++mNumOfCalls;
+ return builder;
+ }
+ }
+
private static class TestView extends View implements TaskFocusStateConsumer {
private TestView(Context context) {
super(context);
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index fe58cca..c708876 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -61,10 +61,12 @@
private static IAudioService sService;
- private final String mPackageName;
+ private final @NonNull String mPackageName;
+ private final @Nullable String mAttributionTag;
public AudioDeviceVolumeManager(Context context) {
mPackageName = context.getApplicationContext().getOpPackageName();
+ mAttributionTag = context.getApplicationContext().getAttributionTag();
}
/**
@@ -287,7 +289,6 @@
* @hide
* Removes a previously added listener of changes to device volume behavior.
*/
-
@RequiresPermission(anyOf = {
android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.QUERY_AUDIO_STATE
@@ -299,6 +300,21 @@
}
/**
+ * @hide
+ * Sets the volume on the given audio device
+ * @param vi the volume information, only stream-based volumes are supported
+ * @param ada the device for which volume is to be modified
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
+ try {
+ getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return human-readable name for volume behavior
* @param behavior one of the volume behaviors defined in AudioManager
* @return a string for the given behavior
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e28178a..90eb9e6 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -98,6 +98,9 @@
void setStreamVolumeWithAttribution(int streamType, int index, int flags,
in String callingPackage, in String attributionTag);
+ void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
+ in String callingPackage, in String attributionTag);
+
oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv,
String callingPackage, String caller);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 58944f6..4714ff9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -576,6 +576,15 @@
return;
}
+ // The profiles list's sequence will affect the bluetooth icon at
+ // BluetoothUtils.getBtClassDrawableWithDescription(Context,CachedBluetoothDevice).
+
+ // Moving the LE audio profile to be the first priority if the device supports LE audio.
+ if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
+ profiles.add(mLeAudioProfile);
+ removedProfiles.remove(mLeAudioProfile);
+ }
+
if (mHeadsetProfile != null) {
if ((ArrayUtils.contains(localUuids, BluetoothUuid.HSP_AG)
&& ArrayUtils.contains(uuids, BluetoothUuid.HSP))
@@ -660,11 +669,6 @@
removedProfiles.remove(mHearingAidProfile);
}
- if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
- profiles.add(mLeAudioProfile);
- removedProfiles.remove(mLeAudioProfile);
- }
-
if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) {
profiles.add(mSapProfile);
removedProfiles.remove(mSapProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 281501e..f4355c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -84,7 +84,8 @@
private InfoMediaManager mInfoMediaManager;
private String mPackageName;
private MediaDevice mOnTransferBluetoothDevice;
- private AudioManager mAudioManager;
+ @VisibleForTesting
+ AudioManager mAudioManager;
@VisibleForTesting
List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 4332bd2..45c0d78 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -57,6 +57,7 @@
private final Handler mHandler;
private final Handler mMainThreadHandler;
private final Set<Integer> mNetworks = new HashSet<>();
+ private int mPrimaryNetworkId;
// Save the previous HISTORY_SIZE states for logging.
private final String[] mHistory = new String[HISTORY_SIZE];
// Where to copy the next state into.
@@ -106,6 +107,7 @@
if (!mNetworks.contains(network.getNetId())) {
mNetworks.add(network.getNetId());
}
+ mPrimaryNetworkId = network.getNetId();
updateWifiInfo(wifiInfo);
updateStatusLabel();
mMainThreadHandler.post(() -> postResults());
@@ -121,10 +123,13 @@
recordLastWifiNetwork(log);
if (mNetworks.contains(network.getNetId())) {
mNetworks.remove(network.getNetId());
- updateWifiInfo(null);
- updateStatusLabel();
- mMainThreadHandler.post(() -> postResults());
}
+ if (network.getNetId() != mPrimaryNetworkId) {
+ return;
+ }
+ updateWifiInfo(null);
+ updateStatusLabel();
+ mMainThreadHandler.post(() -> postResults());
}
};
private final NetworkCallback mDefaultNetworkCallback =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 8e850b2..24bb1bc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static com.google.common.truth.Truth.assertThat;
@@ -29,10 +28,12 @@
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
@@ -71,6 +72,7 @@
private static final String TEST_CURRENT_DEVICE_ID = "currentDevice_id";
private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
private static final String TEST_SESSION_ID = "session_id";
+ private static final String TEST_ADDRESS = "00:01:02:03:04:05";
@Mock
private InfoMediaManager mInfoMediaManager;
@@ -90,6 +92,8 @@
private MediaRoute2Info mRouteInfo1;
@Mock
private MediaRoute2Info mRouteInfo2;
+ @Mock
+ private AudioManager mAudioManager;
private Context mContext;
private LocalMediaManager mLocalMediaManager;
@@ -118,6 +122,7 @@
TEST_PACKAGE_NAME);
mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager.mAudioManager = mAudioManager;
}
@Test
@@ -551,16 +556,12 @@
}
@Test
- public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() {
+ public void onDeviceListAdded_haveMutingExpectedDevice_addMutingExpectedDevice() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
- final MediaDevice device2 = mock(MediaDevice.class);
- final MediaDevice device3 = mock(MediaDevice.class);
mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
devices.add(device1);
- devices.add(device2);
- mLocalMediaManager.mMediaDevices.add(device3);
- mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+ devices.add(mLocalMediaManager.mPhoneDevice);
final List<LocalBluetoothProfile> profiles = new ArrayList<>();
final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
@@ -573,22 +574,25 @@
bluetoothDevices.add(bluetoothDevice);
mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
+ AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, TEST_ADDRESS);
+
+ when(mAudioManager.getMutingExpectedDevice()).thenReturn(audioDeviceAttributes);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
+ when(cachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>());
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(0);
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
@@ -634,68 +638,6 @@
}
@Test
- public void onDeviceListAdded_haveDisconnectedDevice_list5DisconnectedDevice() {
- final List<MediaDevice> devices = new ArrayList<>();
- final MediaDevice device1 = mock(MediaDevice.class);
- final MediaDevice device2 = mock(MediaDevice.class);
- final MediaDevice device3 = mock(MediaDevice.class);
- mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
- devices.add(device1);
- devices.add(device2);
- mLocalMediaManager.mMediaDevices.add(device3);
- mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
-
- final List<LocalBluetoothProfile> profiles = new ArrayList<>();
- final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
- profiles.add(a2dpProfile);
-
- final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
- final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice3 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice4 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice5 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice6 = mock(BluetoothDevice.class);
- final BluetoothClass bluetoothClass = mock(BluetoothClass.class);
- final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
- final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
- bluetoothDevices.add(bluetoothDevice);
- bluetoothDevices.add(bluetoothDevice2);
- bluetoothDevices.add(bluetoothDevice3);
- bluetoothDevices.add(bluetoothDevice4);
- bluetoothDevices.add(bluetoothDevice5);
- bluetoothDevices.add(bluetoothDevice6);
- mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
-
- when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
- when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice2)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice3)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice4)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice5)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice6)).thenReturn(cachedDevice);
- when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(cachedDevice.isConnected()).thenReturn(false);
- when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
- when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
- when(bluetoothDevice.getBluetoothClass()).thenReturn(bluetoothClass);
- when(bluetoothClass.getDeviceClass()).thenReturn(AUDIO_VIDEO_HEADPHONES);
-
- when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
- when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
- when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
-
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
- mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
-
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(7);
- verify(mCallback).onDeviceListUpdate(any());
- }
-
- @Test
public void onDeviceListAdded_bluetoothAdapterIsNull_noDisconnectedDeviceAdded() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
new file mode 100644
index 0000000..dc7e313d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiStatusTrackerTest {
+ @Mock Context mContext;
+ @Mock WifiManager mWifiManager;
+ @Mock NetworkScoreManager mNetworkScoreManager;
+ @Mock ConnectivityManager mConnectivityManager;
+ @Mock Runnable mCallback;
+
+ private final ArgumentCaptor<ConnectivityManager.NetworkCallback>
+ mNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Verify that we only clear the WifiInfo if the primary network was lost.
+ */
+ @Test
+ public void testWifiInfoClearedOnPrimaryNetworkLost() {
+ WifiStatusTracker wifiStatusTracker = new WifiStatusTracker(mContext, mWifiManager,
+ mNetworkScoreManager, mConnectivityManager, mCallback);
+ wifiStatusTracker.setListening(true);
+
+ verify(mConnectivityManager)
+ .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture(), any());
+
+ // Trigger a validation callback for the primary Wifi network.
+ WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+ when(primaryWifiInfo.makeCopy(anyLong())).thenReturn(primaryWifiInfo);
+ when(primaryWifiInfo.isPrimary()).thenReturn(true);
+ int primaryRssi = -55;
+ when(primaryWifiInfo.getRssi()).thenReturn(primaryRssi);
+ NetworkCapabilities primaryCap = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(primaryWifiInfo)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build();
+ Network primaryNetwork = Mockito.mock(Network.class);
+ int primaryNetworkId = 1;
+ when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+ mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+ // Verify primary wifi info is the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Trigger a validation callback for the non-primary Wifi network.
+ WifiInfo nonPrimaryWifiInfo = Mockito.mock(WifiInfo.class);
+ when(nonPrimaryWifiInfo.makeCopy(anyLong())).thenReturn(nonPrimaryWifiInfo);
+ when(nonPrimaryWifiInfo.isPrimary()).thenReturn(false);
+ int nonPrimaryRssi = -75;
+ when(primaryWifiInfo.getRssi()).thenReturn(nonPrimaryRssi);
+ NetworkCapabilities nonPrimaryCap = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(nonPrimaryWifiInfo)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build();
+ Network nonPrimaryNetwork = Mockito.mock(Network.class);
+ int nonPrimaryNetworkId = 2;
+ when(nonPrimaryNetwork.getNetId()).thenReturn(nonPrimaryNetworkId);
+ mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(nonPrimaryNetwork, nonPrimaryCap);
+
+ // Verify primary wifi info is still the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Lose the non-primary network.
+ mNetworkCallbackCaptor.getValue().onLost(nonPrimaryNetwork);
+
+ // Verify primary wifi info is still the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Lose the primary network.
+ mNetworkCallbackCaptor.getValue().onLost(primaryNetwork);
+
+ // Verify we aren't connected anymore.
+ assertThat(wifiStatusTracker.connected).isFalse();
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index ce33160..d1f10a6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -118,6 +118,10 @@
Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index e82bf04..fbbdd32 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -51,6 +51,7 @@
|| (val == BatteryManager.BATTERY_PLUGGED_AC)
|| (val == BatteryManager.BATTERY_PLUGGED_USB)
|| (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+ || (val == BatteryManager.BATTERY_PLUGGED_DOCK)
|| (val
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_USB))
@@ -58,12 +59,29 @@
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_WIRELESS))
|| (val
+ == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
== (BatteryManager.BATTERY_PLUGGED_USB
| BatteryManager.BATTERY_PLUGGED_WIRELESS))
|| (val
+ == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_WIRELESS
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_USB
- | BatteryManager.BATTERY_PLUGGED_WIRELESS));
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS
+ | BatteryManager.BATTERY_PLUGGED_DOCK));
} catch (NumberFormatException e) {
return false;
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 5d77378..4aadf72 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -172,6 +172,11 @@
Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
+ NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index ccfeae4..aa3a983 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2358,6 +2358,12 @@
SecureSettingsProto.PowerMenuPrivacy.SHOW);
p.end(powerMenuPrivacyToken);
+ final long extraLowPowerModeToken = p.start(SecureSettingsProto.EXTRA_LOW_POWER_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.EXTRA_AUTOMATIC_POWER_SAVE_MODE,
+ SecureSettingsProto.ExtraLowPowerMode.EXTRA_AUTOMATIC_POWER_SAVE_MODE);
+ p.end(extraLowPowerModeToken);
+
final long printServiceToken = p.start(SecureSettingsProto.PRINT_SERVICE);
dumpSetting(s, p,
Settings.Secure.PRINT_SERVICE_SEARCH_URI,
diff --git a/packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 962c002..3d341af 100644
--- a/packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
@@ -61,7 +61,7 @@
var index: Int,
val sortedAxes: MutableList<FontVariationAxis>
) {
- constructor(font: Font, axes: List<FontVariationAxis>):
+ constructor(font: Font, axes: List<FontVariationAxis>) :
this(font.sourceIdentifier,
font.ttcIndex,
axes.toMutableList().apply { sortBy { it.tag } }
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index ade89af..f79b328 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -61,8 +61,8 @@
private val invalidateCallback: () -> Unit
) {
// Following two members are for mutable for testing purposes.
- internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
- internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
+ public var textInterpolator: TextInterpolator = TextInterpolator(layout)
+ public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
textInterpolator.progress = it.animatedValue as Float
@@ -279,4 +279,4 @@
put(key, v)
}
return v
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 20dbe29..ff64c78 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.Canvas
import android.graphics.Paint
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
new file mode 100644
index 0000000..0c191607
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.plugins
+
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.systemui.plugins.annotations.ProvidesInterface
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+
+/** Identifies a clock design */
+typealias ClockId = String
+
+/** A Plugin which exposes the ClockProvider interface */
+@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
+interface ClockProviderPlugin : Plugin, ClockProvider {
+ companion object {
+ const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
+ const val VERSION = 1
+ }
+}
+
+/** Interface for building clocks and providing information about those clocks */
+interface ClockProvider {
+ /** Returns metadata for all clocks this provider knows about */
+ fun getClocks(): List<ClockMetadata>
+
+ /** Initializes and returns the target clock design */
+ fun createClock(id: ClockId): Clock
+
+ /** A static thumbnail for rendering in some examples */
+ fun getClockThumbnail(id: ClockId): Drawable?
+}
+
+/** Interface for controlling an active clock */
+interface Clock {
+ /** A small version of the clock, appropriate for smaller viewports */
+ val smallClock: View
+
+ /** A large version of the clock, appropriate when a bigger viewport is available */
+ val largeClock: View
+
+ /** Events that clocks may need to respond to */
+ val events: ClockEvents
+
+ /** Triggers for various animations */
+ val animation: ClockAnimation
+
+ /** Optional method for dumping debug information */
+ fun dump(pw: PrintWriter) { }
+}
+
+/** Events that should call when various rendering parameters change */
+interface ClockEvents {
+ /** Call every time tick */
+ fun onTimeTick()
+
+ /** Call whenever timezone changes */
+ fun onTimeZoneChanged(timeZone: TimeZone) { }
+
+ /** Call whenever the text time format changes (12hr vs 24hr) */
+ fun onTimeFormatChanged(is24Hr: Boolean) { }
+
+ /** Call whenever the locale changes */
+ fun onLocaleChanged(locale: Locale) { }
+
+ /** Call whenever font settings change */
+ fun onFontSettingChanged() { }
+
+ /** Call whenever the color pallete should update */
+ fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
+}
+
+/** Methods which trigger various clock animations */
+interface ClockAnimation {
+ /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
+ fun initialize(dozeFraction: Float, foldFraction: Float) { }
+
+ /** Runs an enter animation (if any) */
+ fun enter() { }
+
+ /** Sets how far into AOD the device currently is. */
+ fun doze(fraction: Float) { }
+
+ /** Sets how far into the folding animation the device is. */
+ fun fold(fraction: Float) { }
+
+ /** Runs the battery animation (if any). */
+ fun charge() { }
+}
+
+/** Some data about a clock design */
+data class ClockMetadata(
+ val clockId: ClockId,
+ val name: String
+)
diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
index d0867e9..0137dc3 100644
--- a/packages/SystemUI/res-keyguard/font/clock.xml
+++ b/packages/SystemUI/res-keyguard/font/clock.xml
@@ -22,6 +22,7 @@
** Should include all numeric glyphs in all supported locales.
** Recommended: font with variable width to support AOD => LS animations
-->
+<!-- TODO: Remove when clock migration complete -->
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font android:typeface="monospace"/>
</font-family>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 87a9825..6a38507 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,7 +31,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:paddingStart="@dimen/clock_padding_start">
- <com.android.keyguard.AnimatableClockView
+ <com.android.systemui.shared.clocks.AnimatableClockView
android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -53,7 +53,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/keyguard_slice_view"
android:visibility="gone">
- <com.android.keyguard.AnimatableClockView
+ <com.android.systemui.shared.clocks.AnimatableClockView
android:id="@+id/animatable_clock_view_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 7ce6f0e..712f657 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -48,10 +48,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintTop_toTopOf="parent"
androidprv:layout_constraintEnd_toEndOf="parent"
androidprv:layout_constraintStart_toStartOf="parent"
androidprv:layout_constraintBottom_toTopOf="@id/key1"
- androidprv:layout_constraintVertical_bias="0.0">
+ androidprv:layout_constraintVertical_bias="1.0">
<com.android.keyguard.PasswordTextView
android:id="@+id/pinEntry"
diff --git a/packages/SystemUI/res-keyguard/values/attrs.xml b/packages/SystemUI/res-keyguard/values/attrs.xml
index 25be37a..f2a1e26 100644
--- a/packages/SystemUI/res-keyguard/values/attrs.xml
+++ b/packages/SystemUI/res-keyguard/values/attrs.xml
@@ -41,10 +41,4 @@
<attr name="passwordStyle" format="reference" />
<attr name="numPadKeyStyle" format="reference" />
-
- <declare-styleable name="AnimatableClockView">
- <attr name="dozeWeight" format="integer" />
- <attr name="lockScreenWeight" format="integer" />
- <attr name="chargeAnimationDelay" format="integer" />
- </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index e677797..f9872f8 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -23,11 +23,5 @@
<!-- Skeleton string format for displaying the date shorter. -->
<string name="abbrev_month_day_no_year">MMMd</string>
- <!-- Skeleton string format for displaying the time in 12-hour format. -->
- <string name="clock_12hr_format">hm</string>
-
- <!-- Skeleton string format for displaying the time in 24-hour format. -->
- <string name="clock_24hr_format">Hm</string>
-
<string name="num_pad_key_ratio">1</string>
</resources>
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
new file mode 100644
index 0000000..4f0a78e
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/home_controls_chip"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|start"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:src="@drawable/controls_icon"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:contentDescription="@string/quick_controls_title" />
diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
index e929169..a97c90c 100644
--- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
+++ b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
@@ -54,7 +54,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/rounded_bg_full_large_radius"
- android:onClick="dismissActivity"
android:text="@string/got_it"
android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginBottom="60dp"
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index cf49728..6f87169 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -44,7 +44,6 @@
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>
<item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
- <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
</style>
<style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0cafa35..3fb00a3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -59,8 +59,8 @@
<dimen name="navigation_edge_entry_margin">4dp</dimen>
<dimen name="navigation_edge_entry_background_width">8dp</dimen>
<dimen name="navigation_edge_entry_background_height">60dp</dimen>
- <dimen name="navigation_edge_entry_edge_corners">6dp</dimen>
- <dimen name="navigation_edge_entry_far_corners">6dp</dimen>
+ <dimen name="navigation_edge_entry_edge_corners">30dp</dimen>
+ <dimen name="navigation_edge_entry_far_corners">30dp</dimen>
<dimen name="navigation_edge_entry_arrow_length">10dp</dimen>
<dimen name="navigation_edge_entry_arrow_height">7dp</dimen>
@@ -90,6 +90,7 @@
<dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
+ <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen>
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
@@ -665,6 +666,7 @@
<!-- With the large clock, move up slightly from the center -->
<dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+ <!-- TODO: Remove during migration -->
<!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
<!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
@@ -889,6 +891,7 @@
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <!-- TODO: Remove when clock migration complete -->
<dimen name="large_clock_text_size">150dp</dimen>
<dimen name="clock_text_size">86dp</dimen>
@@ -898,7 +901,7 @@
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
direction that elements are moved to prevent burn-in on AOD-->
<dimen name="udfps_burn_in_offset_x">7px</dimen>
- <dimen name="udfps_burn_in_offset_y">28px</dimen>
+ <dimen name="udfps_burn_in_offset_y">20px</dimen>
<!-- The absolute side margins of quick settings -->
<dimen name="quick_settings_bottom_margin_media">8dp</dimen>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 114ea65..18bd6b4 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -47,6 +47,8 @@
],
static_libs: [
"PluginCoreLib",
+ "SystemUIAnimationLib",
+ "SystemUIPluginLib",
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
@@ -54,6 +56,9 @@
"dagger2",
"jsr330",
],
+ resource_dirs: [
+ "res",
+ ],
java_version: "1.8",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
new file mode 100644
index 0000000..be72d0b
--- /dev/null
+++ b/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#FFFF00FF" />
+</shape>
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
new file mode 100644
index 0000000..8510a0a
--- /dev/null
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -0,0 +1,32 @@
+<?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.shared.clocks.AnimatableClockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animatable_clock_view_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal"
+ android:textSize="@dimen/large_clock_text_size"
+ android:fontFamily="@*android:string/config_clockFontFamily"
+ android:typeface="monospace"
+ android:elegantTextHeight="false"
+ chargeAnimationDelay="200"
+ dozeWeight="200"
+ lockScreenWeight="400" />
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
new file mode 100644
index 0000000..ec0e427
--- /dev/null
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -0,0 +1,33 @@
+<?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.shared.clocks.AnimatableClockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animatable_clock_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:gravity="start"
+ android:textSize="@dimen/small_clock_text_size"
+ android:fontFamily="@*android:string/config_clockFontFamily"
+ android:elegantTextHeight="false"
+ android:singleLine="true"
+ android:fontFeatureSettings="pnum"
+ chargeAnimationDelay="350"
+ dozeWeight="200"
+ lockScreenWeight="400" />
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
new file mode 100644
index 0000000..f9d66ee
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+ the documentation output. To suppress comment lines from the documentation
+ output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+ <declare-styleable name="AnimatableClockView">
+ <attr name="dozeWeight" format="integer" />
+ <attr name="lockScreenWeight" format="integer" />
+ <attr name="chargeAnimationDelay" format="integer" />
+ </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/shared/res/values/dimens.xml
new file mode 100644
index 0000000..8f90f0f
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+<resources>
+ <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <dimen name="large_clock_text_size">150sp</dimen>
+ <dimen name="small_clock_text_size">86sp</dimen>
+
+ <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+ <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/shared/res/values/donottranslate.xml
new file mode 100644
index 0000000..383d5521
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Skeleton string format for displaying the time in 12-hour format. -->
+ <string name="clock_12hr_format">hm</string>
+
+ <!-- Skeleton string format for displaying the time in 24-hour format. -->
+ <string name="clock_24hr_format">Hm</string>
+</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
similarity index 90%
rename from packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index b4955d2..2739d59 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.shared.clocks
import android.animation.TimeInterpolator
import android.annotation.ColorInt
@@ -26,9 +26,10 @@
import android.text.format.DateFormat
import android.util.AttributeSet
import android.widget.TextView
-import com.android.systemui.R
+import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.animation.TextAnimator
+import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
@@ -38,13 +39,13 @@
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
* The time's text color is a gradient that changes its colors based on its controller.
*/
+@SuppressLint("AppCompatCustomView")
class AnimatableClockView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- private val tag = "AnimatableClockView"
private var lastMeasureCall: CharSequence = ""
@@ -193,7 +194,7 @@
)
}
- fun animateFoldAppear() {
+ fun animateFoldAppear(animate: Boolean = true) {
if (textAnimator == null) {
return
}
@@ -210,22 +211,22 @@
weight = dozingWeightInternal,
textSize = -1f,
color = dozingColor,
- animate = true,
+ animate = animate,
interpolator = Interpolators.EMPHASIZED_DECELERATE,
- duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
+ duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
delay = 0,
onAnimationEnd = null
)
}
- fun animateCharge(dozeStateGetter: DozeStateGetter) {
+ fun animateCharge(isDozing: () -> Boolean) {
if (textAnimator == null || textAnimator!!.isRunning()) {
// Skip charge animation if dozing animation is already playing.
return
}
val startAnimPhase2 = Runnable {
setTextStyle(
- weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight,
+ weight = if (isDozing()) dozingWeight else lockScreenWeight,
textSize = -1f,
color = null,
animate = true,
@@ -235,7 +236,7 @@
)
}
setTextStyle(
- weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight,
+ weight = if (isDozing()) lockScreenWeight else dozingWeight,
textSize = -1f,
color = null,
animate = true,
@@ -330,9 +331,9 @@
)
}
- fun refreshFormat() {
+ fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context))
+ fun refreshFormat(use24HourFormat: Boolean) {
Patterns.update(context)
- val use24HourFormat = DateFormat.is24HourFormat(context)
format = when {
isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
@@ -385,14 +386,14 @@
}
}
- interface DozeStateGetter {
- val isDozing: Boolean
+ companion object {
+ private val TAG = AnimatableClockView::class.simpleName
+ const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
+ private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
+ private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
+ private const val DOZE_ANIM_DURATION: Long = 300
+ private const val APPEAR_ANIM_DURATION: Long = 350
+ private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
+ private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
}
}
-
-private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
-private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
-private const val DOZE_ANIM_DURATION: Long = 300
-private const val APPEAR_ANIM_DURATION: Long = 350
-private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
-private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
deleted file mode 100644
index 916a557..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
+++ /dev/null
@@ -1,65 +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.shared.clocks
-
-import com.android.systemui.plugins.Plugin
-import com.android.systemui.plugins.annotations.ProvidesInterface
-import android.annotation.FloatRange
-import android.graphics.drawable.Drawable
-import android.view.View
-
-/** Identifies a clock design */
-typealias ClockId = String
-
-/** A Plugin which exposes the ClockProvider interface */
-@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
-interface ClockProviderPlugin : Plugin, ClockProvider {
- companion object {
- const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
- const val VERSION = 1
- }
-}
-
-/** Interface for building clocks and providing information about those clocks */
-interface ClockProvider {
- /** Returns metadata for all clocks this provider knows about */
- fun getClocks(): List<ClockMetadata>
-
- /** Initializes and returns the target clock design */
- fun createClock(id: ClockId): Clock
-
- /** A static thumbnail for rendering in some examples */
- fun getClockThumbnail(id: ClockId): Drawable?
-}
-
-/** Interface for controlling an active clock */
-interface Clock {
- /** A small version of the clock, appropriate for smaller viewports */
- val smallClock: View
-
- /** A large version of the clock, appropriate when a bigger viewport is available */
- val largeClock: View
-
- /** Callback to update the clock view to the current time */
- fun onTimeTick()
-
- /** Sets the level of the AOD transition */
- fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float)
-}
-
-/** Some data about a clock design */
-data class ClockMetadata(
- val clockId: ClockId,
- val name: String
-)
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 3245966..a4c03b0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -22,6 +22,11 @@
import android.provider.Settings
import android.util.Log
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import com.google.gson.Gson
@@ -29,16 +34,23 @@
private val TAG = ClockRegistry::class.simpleName
private val DEBUG = true
-const val DEFAULT_CLOCK_ID = "DEFAULT"
typealias ClockChangeListener = () -> Unit
/** ClockRegistry aggregates providers and plugins */
-open class ClockRegistry @Inject constructor(
+open class ClockRegistry(
val context: Context,
val pluginManager: PluginManager,
- @Main val handler: Handler
+ val handler: Handler,
+ defaultClockProvider: ClockProvider
) {
+ @Inject constructor(
+ context: Context,
+ pluginManager: PluginManager,
+ @Main handler: Handler,
+ defaultClockProvider: DefaultClockProvider
+ ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
+
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
@@ -48,60 +60,71 @@
}
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
- override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
- val currentId = currentClockId
- for (clock in plugin.getClocks()) {
- val id = clock.clockId
- val current = availableClocks[id]
- if (current != null) {
- Log.e(TAG, "Clock Id conflict: $id is registered by both " +
- "${plugin::class.simpleName} and ${current.provider::class.simpleName}")
- return
- }
+ override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
+ connectClocks(plugin)
- availableClocks[id] = ClockInfo(clock, plugin)
-
- if (currentId == id) {
- if (DEBUG) {
- Log.i(TAG, "Current clock ($currentId) was connected")
- }
- clockChangeListeners.forEach { it() }
- }
- }
- }
-
- override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
- val currentId = currentClockId
- for (clock in plugin.getClocks()) {
- availableClocks.remove(clock.clockId)
-
- if (currentId == clock.clockId) {
- Log.w(TAG, "Current clock ($currentId) was disconnected")
- clockChangeListeners.forEach { it() }
- }
- }
- }
+ override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
+ disconnectClocks(plugin)
}
open var currentClockId: ClockId
get() {
- val json = Settings.Secure.getString(context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE)
+ val json = Settings.Secure.getString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ )
return gson.fromJson(json, ClockSetting::class.java).clockId
}
set(value) {
val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
- Settings.Secure.putString(context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json)
+ Settings.Secure.putString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+ )
}
init {
+ connectClocks(defaultClockProvider)
pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
false,
settingObserver,
- UserHandle.USER_ALL)
+ UserHandle.USER_ALL
+ )
+ }
+
+ private fun connectClocks(provider: ClockProvider) {
+ val currentId = currentClockId
+ for (clock in provider.getClocks()) {
+ val id = clock.clockId
+ val current = availableClocks[id]
+ if (current != null) {
+ Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+ "${provider::class.simpleName} and ${current.provider::class.simpleName}")
+ return
+ }
+
+ availableClocks[id] = ClockInfo(clock, provider)
+ if (currentId == id) {
+ if (DEBUG) {
+ Log.i(TAG, "Current clock ($currentId) was connected")
+ }
+ clockChangeListeners.forEach { it() }
+ }
+ }
+ }
+
+ private fun disconnectClocks(provider: ClockProvider) {
+ val currentId = currentClockId
+ for (clock in provider.getClocks()) {
+ availableClocks.remove(clock.clockId)
+
+ if (currentId == clock.clockId) {
+ Log.w(TAG, "Current clock ($currentId) was disconnected")
+ clockChangeListeners.forEach { it() }
+ }
+ }
}
fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
@@ -143,4 +166,4 @@
val clockId: ClockId,
val _applied_timestamp: Long
)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
new file mode 100644
index 0000000..5d8da59
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.shared.clocks
+
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.icu.text.NumberFormat
+import android.util.TypedValue
+import android.view.LayoutInflater
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockAnimation
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.shared.R
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import javax.inject.Inject
+
+private val TAG = DefaultClockProvider::class.simpleName
+const val DEFAULT_CLOCK_NAME = "Default Clock"
+const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+/** Provides the default system clock */
+class DefaultClockProvider @Inject constructor(
+ val layoutInflater: LayoutInflater,
+ @Main val resources: Resources
+) : ClockProvider {
+ override fun getClocks(): List<ClockMetadata> =
+ listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
+
+ override fun createClock(id: ClockId): Clock {
+ if (id != DEFAULT_CLOCK_ID) {
+ throw IllegalArgumentException("$id is unsupported by $TAG")
+ }
+ return DefaultClock(layoutInflater, resources)
+ }
+
+ override fun getClockThumbnail(id: ClockId): Drawable? {
+ if (id != DEFAULT_CLOCK_ID) {
+ throw IllegalArgumentException("$id is unsupported by $TAG")
+ }
+
+ // TODO: Update placeholder to actual resource
+ return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+ }
+}
+
+/**
+ * Controls the default clock visuals.
+ *
+ * This serves as an adapter between the clock interface and the
+ * AnimatableClockView used by the existing lockscreen clock.
+ */
+class DefaultClock(
+ private val layoutInflater: LayoutInflater,
+ private val resources: Resources
+) : Clock {
+ override val smallClock =
+ layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
+ override val largeClock =
+ layoutInflater.inflate(R.layout.clock_default_large, null) as AnimatableClockView
+ private val clocks = listOf(smallClock, largeClock)
+
+ private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
+ private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
+ private val burmeseLineSpacing =
+ resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
+ private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+
+ override val events = object : ClockEvents {
+ override fun onTimeTick() = clocks.forEach { it.refreshTime() }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) =
+ clocks.forEach { it.refreshFormat(is24Hr) }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) =
+ clocks.forEach { it.onTimeZoneChanged(timeZone) }
+
+ override fun onFontSettingChanged() {
+ smallClock.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ largeClock.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
+ }
+
+ override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
+ clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
+
+ override fun onLocaleChanged(locale: Locale) {
+ val nf = NumberFormat.getInstance(locale)
+ if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
+ clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
+ } else {
+ clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
+ }
+
+ clocks.forEach { it.refreshFormat() }
+ }
+ }
+
+ override val animation = object : ClockAnimation {
+ override fun initialize(dozeFraction: Float, foldFraction: Float) {
+ dozeState = AnimationState(dozeFraction)
+ foldState = AnimationState(foldFraction)
+
+ if (foldState.isActive) {
+ clocks.forEach { it.animateFoldAppear(false) }
+ } else {
+ clocks.forEach { it.animateDoze(dozeState.isActive, false) }
+ }
+ }
+
+ override fun enter() {
+ if (dozeState.isActive) {
+ clocks.forEach { it.animateAppearOnLockscreen() }
+ }
+ }
+
+ override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
+
+ private var foldState = AnimationState(0f)
+ override fun fold(fraction: Float) {
+ val (hasChanged, hasJumped) = foldState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateFoldAppear(!hasJumped) }
+ }
+ }
+
+ private var dozeState = AnimationState(0f)
+ override fun doze(fraction: Float) {
+ val (hasChanged, hasJumped) = dozeState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
+ }
+ }
+ }
+
+ private class AnimationState(
+ var fraction: Float
+ ) {
+ var isActive: Boolean = fraction < 0.5f
+ fun update(newFraction: Float): Pair<Boolean, Boolean> {
+ val wasActive = isActive
+ val hasJumped = (fraction == 0f && newFraction == 1f) ||
+ (fraction == 1f && newFraction == 0f)
+ isActive = newFraction > fraction
+ fraction = newFraction
+ return Pair(wasActive != isActive, hasJumped)
+ }
+ }
+
+ init {
+ events.onLocaleChanged(Locale.getDefault())
+ }
+
+ override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
+
+ companion object {
+ private const val DOZE_COLOR = Color.WHITE
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index e3f5687..4222744 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -16,6 +16,7 @@
package com.android.systemui.shared.recents.model;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
@@ -245,12 +246,16 @@
*/
public static Task from(TaskKey taskKey, TaskInfo taskInfo, boolean isLocked) {
ActivityManager.TaskDescription td = taskInfo.taskDescription;
+ // Also consider undefined activity type to include tasks in overview right after rebooting
+ // the device.
+ final boolean isDockable = taskInfo.supportsMultiWindow
+ && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode())
+ && (taskInfo.getActivityType() == ACTIVITY_TYPE_UNDEFINED
+ || ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()));
return new Task(taskKey,
td != null ? td.getPrimaryColor() : 0,
- td != null ? td.getBackgroundColor() : 0, taskInfo.supportsMultiWindow
- && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
- && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()),
- isLocked, td, taskInfo.topActivity);
+ td != null ? td.getBackgroundColor() : 0, isDockable , isLocked, td,
+ taskInfo.topActivity);
}
public Task(TaskKey key) {
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 487e1a4..c69ff7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -32,6 +32,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
@@ -134,6 +135,21 @@
reset();
}
}
+
+ @Override
+ public void onTimeFormatChanged(String timeFormat) {
+ mView.refreshFormat();
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mView.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ mView.refreshFormat();
+ }
};
@Override
@@ -186,7 +202,7 @@
/** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
* fully folded state and it goes to sleep (always on display screen) */
public void animateFoldAppear() {
- mView.animateFoldAppear();
+ mView.animateFoldAppear(true);
}
/**
@@ -197,20 +213,6 @@
}
/**
- * Updates the timezone for the view.
- */
- public void onTimeZoneChanged(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
- /**
- * Trigger a time format update
- */
- public void refreshFormat() {
- mView.refreshFormat();
- }
-
- /**
* Return locallly stored dozing state.
*/
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index f289105..c5190e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -35,7 +35,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.dagger.KeyguardBouncerScope;
-import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.EmergencyDialerConstants;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 40edfe5..206b8be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -22,6 +22,7 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5c9dd5e..6c32a49 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -398,17 +398,6 @@
void updateTimeZone(TimeZone timeZone) {
mView.onTimeZoneChanged(timeZone);
- if (mClockViewController != null) {
- mClockViewController.onTimeZoneChanged(timeZone);
- mLargeClockViewController.onTimeZoneChanged(timeZone);
- }
- }
-
- void refreshFormat() {
- if (mClockViewController != null) {
- mClockViewController.refreshFormat();
- mLargeClockViewController.refreshFormat();
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8921780..014d082 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -240,11 +240,6 @@
}
@Override
- public void onTimeFormatChanged(String timeFormat) {
- mKeyguardClockSwitchController.refreshFormat();
- }
-
- @Override
public void onTimeZoneChanged(TimeZone timeZone) {
mKeyguardClockSwitchController.updateTimeZone(timeZone);
}
@@ -256,11 +251,6 @@
refreshTime();
}
}
-
- @Override
- public void onUserSwitchComplete(int userId) {
- mKeyguardClockSwitchController.refreshFormat();
- }
};
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index c191757..4ae2cad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -805,8 +805,8 @@
private Runnable mRetryFingerprintAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " +
- mHardwareFingerprintUnavailableRetryCount);
+ Log.w(TAG,
+ "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
if (mFpm.isHardwareDetected()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -833,7 +833,9 @@
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
- if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
+ if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+ || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
+ Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1270bd8..720d708 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -70,6 +70,7 @@
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -104,7 +105,6 @@
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index f99293a..edcaf49 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -842,7 +842,8 @@
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_SLIPPERY
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index aa67ecd..e16ac08 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -169,6 +169,7 @@
private Animator mExitAnimator;
private Animator mEnterAnimator;
private final int mOrientation;
+ private boolean mKeyboardVisible;
public ClipboardOverlayController(Context context,
@@ -261,8 +262,22 @@
attachWindow();
withWindowAttached(() -> {
mWindow.setContentView(mView);
- updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
- mView.requestLayout();
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ updateInsets(insets);
+ mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ WindowInsets insets =
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ if (keyboardVisible != mKeyboardVisible) {
+ mKeyboardVisible = keyboardVisible;
+ updateInsets(insets);
+ }
+ }
+ });
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
@@ -384,8 +399,6 @@
mRemoteCopyChip.setVisibility(View.GONE);
}
withWindowAttached(() -> {
- updateInsets(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
mView.post(this::animateIn);
}
@@ -509,7 +522,7 @@
private void shareContent(ClipData clip) {
mUiEventLogger.log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText());
+ shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString());
shareIntent.setDataAndType(
clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0));
shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri());
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index a174ed0..2389ad1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -62,7 +62,7 @@
backgroundExecutor.execute {
val collator = Collator.getInstance(resources.configuration.locales[0])
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
- it.loadLabel()
+ it.loadLabel() ?: ""
}
listOfServices = serviceInfos.sortedWith(localeComparator)
uiExecutor.execute(::notifyDataSetChanged)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 1268250..c1e99f5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -59,7 +59,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -71,18 +71,18 @@
@SysUISingleton
class ControlsUiControllerImpl @Inject constructor (
- val controlsController: Lazy<ControlsController>,
- val context: Context,
- @Main val uiExecutor: DelayableExecutor,
- @Background val bgExecutor: DelayableExecutor,
- val controlsListingController: Lazy<ControlsListingController>,
- @Main val sharedPreferences: SharedPreferences,
- val controlActionCoordinator: ControlActionCoordinator,
- private val activityStarter: ActivityStarter,
- private val shadeController: ShadeController,
- private val iconCache: CustomIconCache,
- private val controlsMetricsLogger: ControlsMetricsLogger,
- private val keyguardStateController: KeyguardStateController
+ val controlsController: Lazy<ControlsController>,
+ val context: Context,
+ @Main val uiExecutor: DelayableExecutor,
+ @Background val bgExecutor: DelayableExecutor,
+ val controlsListingController: Lazy<ControlsListingController>,
+ @Main val sharedPreferences: SharedPreferences,
+ val controlActionCoordinator: ControlActionCoordinator,
+ private val activityStarter: ActivityStarter,
+ private val shadeController: ShadeController,
+ private val iconCache: CustomIconCache,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val keyguardStateController: KeyguardStateController
) : ControlsUiController {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 2c1463d..a9e47c4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -45,6 +45,9 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -57,9 +60,6 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.ShadeControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 029cabb..718befa 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,6 +31,7 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
@@ -130,6 +131,7 @@
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
+ getConnectivityInfoProcessor();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -212,6 +214,9 @@
/** */
Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
+ /** */
+ Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor();
+
/**
* Returns {@link CoreStartable}s that should be started with the application.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index fe96222..f32ea35 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -51,6 +51,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -68,9 +69,8 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -91,7 +91,6 @@
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.dagger.DynamicOverride;
-import com.android.wm.shell.sysui.ShellController;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -135,6 +134,7 @@
SettingsUtilModule.class,
SmartRepliesInflationModule.class,
SmartspaceModule.class,
+ StatusBarPipelineModule.class,
StatusBarPolicyModule.class,
StatusBarWindowModule.class,
SysUIConcurrencyModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
new file mode 100644
index 0000000..1a9d9b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -0,0 +1,205 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
+import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.controls.ui.ControlsActivity;
+import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * A dream complication that shows a home controls chip to launch device controls (to control
+ * devices at home like lights and thermostats).
+ */
+public class DreamHomeControlsComplication implements Complication {
+ private final DreamHomeControlsComplicationComponent.Factory mComponentFactory;
+
+ @Inject
+ public DreamHomeControlsComplication(
+ DreamHomeControlsComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_NONE;
+ }
+
+ /**
+ * {@link CoreStartable} for registering the complication with SystemUI on startup.
+ */
+ public static class Registrant extends CoreStartable {
+ private final DreamHomeControlsComplication mComplication;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final ControlsComponent mControlsComponent;
+
+ private boolean mControlServicesAvailable = false;
+
+ // Callback for when the home controls service availability changes.
+ private final ControlsListingController.ControlsListingCallback mControlsCallback =
+ serviceInfos -> {
+ boolean available = !serviceInfos.isEmpty();
+
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateComplicationAvailability();
+ }
+ };
+
+ @Inject
+ public Registrant(Context context, DreamHomeControlsComplication complication,
+ DreamOverlayStateController dreamOverlayStateController,
+ ControlsComponent controlsComponent) {
+ super(context);
+
+ mComplication = complication;
+ mControlsComponent = controlsComponent;
+ mDreamOverlayStateController = dreamOverlayStateController;
+ }
+
+ @Override
+ public void start() {
+ mControlsComponent.getControlsListingController().ifPresent(
+ c -> c.addCallback(mControlsCallback));
+ }
+
+ private void updateComplicationAvailability() {
+ final boolean hasFavorites = mControlsComponent.getControlsController()
+ .map(c -> !c.getFavorites().isEmpty())
+ .orElse(false);
+ if (!hasFavorites || !mControlServicesAvailable
+ || mControlsComponent.getVisibility() == UNAVAILABLE) {
+ mDreamOverlayStateController.removeComplication(mComplication);
+ } else {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+ }
+
+ /**
+ * Contains values/logic associated with the dream complication view.
+ */
+ public static class DreamHomeControlsChipViewHolder implements ViewHolder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+ private final DreamHomeControlsChipViewController mViewController;
+
+ @Inject
+ DreamHomeControlsChipViewHolder(
+ DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+ @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
+ ) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ mViewController = dreamHomeControlsChipViewController;
+ mViewController.init();
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+
+ /**
+ * Controls behavior of the dream complication.
+ */
+ static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "DreamHomeControlsCtrl";
+
+ private final ActivityStarter mActivityStarter;
+ private final Context mContext;
+ private final ControlsComponent mControlsComponent;
+
+ @Inject
+ DreamHomeControlsChipViewController(
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+ ActivityStarter activityStarter,
+ Context context,
+ ControlsComponent controlsComponent) {
+ super(view);
+
+ mActivityStarter = activityStarter;
+ mContext = context;
+ mControlsComponent = controlsComponent;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mView.setImageResource(mControlsComponent.getTileImageId());
+ mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+ mView.setOnClickListener(this::onClickHomeControls);
+ }
+
+ @Override
+ protected void onViewDetached() {}
+
+ private void onClickHomeControls(View v) {
+ if (DEBUG) Log.d(TAG, "home controls complication 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);
+
+ final ActivityLaunchAnimator.Controller controller =
+ v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
+ : null;
+ if (mControlsComponent.getVisibility() == AVAILABLE) {
+ // Controls can be made visible.
+ mActivityStarter.startActivity(intent, true /* dismissShade */, controller,
+ true /* showOverLockscreenWhenLocked */);
+ } else if (mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK) {
+ // Controls can be made visible only after device unlock.
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */,
+ controller);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
new file mode 100644
index 0000000..033ce39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -0,0 +1,108 @@
+/*
+ * 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.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.res.Resources;
+import android.view.LayoutInflater;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * Responsible for generating dependencies for the {@link DreamHomeControlsComplication}.
+ */
+@Subcomponent(modules = DreamHomeControlsComplicationComponent.DreamHomeControlsModule.class)
+@DreamHomeControlsComplicationComponent.DreamHomeControlsComplicationScope
+public interface DreamHomeControlsComplicationComponent {
+ /**
+ * Creates a view holder for the home controls complication.
+ */
+ DreamHomeControlsComplication.DreamHomeControlsChipViewHolder getViewHolder();
+
+ /**
+ * Scope of the home controls complication.
+ */
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamHomeControlsComplicationScope {}
+
+ /**
+ * Factory that generates a {@link DreamHomeControlsComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamHomeControlsComplicationComponent create();
+ }
+
+ /**
+ * Scoped injected values for the {@link DreamHomeControlsComplicationComponent}.
+ */
+ @Module
+ interface DreamHomeControlsModule {
+ String DREAM_HOME_CONTROLS_CHIP_VIEW = "dream_home_controls_chip_view";
+ String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
+
+ // TODO(b/217199227): move to a single location.
+ // Weight of order in the parent container. The home controls complication should have low
+ // weight and be placed at the end.
+ int INSERT_ORDER_WEIGHT = 0;
+
+ /**
+ * Provides the dream home controls chip view.
+ */
+ @Provides
+ @DreamHomeControlsComplicationScope
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
+ static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
+ return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+ null, false);
+ }
+
+ /**
+ * Provides the layout parameters for the dream home controls complication.
+ */
+ @Provides
+ @DreamHomeControlsComplicationScope
+ @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams(@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,
+ INSERT_ORDER_WEIGHT);
+ }
+ }
+
+}
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 e45437d..4a515f0 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
@@ -27,6 +27,9 @@
@Module(includes = {
DreamClockDateComplicationModule.class,
DreamClockTimeComplicationModule.class,
+ },
+ subcomponents = {
+ DreamHomeControlsComplicationComponent.class,
})
public interface RegisteredComplicationsModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 65711ed..958a219 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -61,7 +61,7 @@
new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS =
- new BooleanFlag(109, false);
+ new BooleanFlag(109, false, true);
public static final BooleanFlag FSI_REQUIRES_KEYGUARD =
new BooleanFlag(110, false, true);
@@ -148,6 +148,8 @@
public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE =
new BooleanFlag(603, false);
+ public static final BooleanFlag NEW_STATUS_BAR_PIPELINE = new BooleanFlag(604, false);
+
/***************************************/
// 700 - dialer/calls
public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 281ef94..75e48d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -128,6 +128,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
@@ -146,7 +147,6 @@
import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.DeviceConfigProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 2006d53..b05e75e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -601,7 +601,7 @@
if (currentState == GestureState.INACTIVE ||
currentState == GestureState.CANCELLED
)
- params.entryIndicator.backgroundDimens.edgeCornerRadius
+ params.cancelledEdgeCornerRadius
else
params.activeIndicator.backgroundDimens.edgeCornerRadius
)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index a3fb58d..d56537b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -33,6 +33,8 @@
private set
var fullyStretchedIndicator = BackIndicatorDimens()
private set
+ var cancelledEdgeCornerRadius: Float = 0f
+ private set
var cancelledArrowDimens = ArrowDimens()
// navigation bar edge constants
@@ -132,6 +134,8 @@
)
)
+ cancelledEdgeCornerRadius = getDimen(R.dimen.navigation_edge_cancelled_edge_corners)
+
cancelledArrowDimens = ArrowDimens(
length = getDimen(R.dimen.navigation_edge_cancelled_arrow_length),
height = getDimen(R.dimen.navigation_edge_cancelled_arrow_height)
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index e845aa8..7cc95a1 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
-import android.view.View;
import android.view.ViewGroup;
import androidx.activity.ComponentActivity;
@@ -40,7 +39,6 @@
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private final PeopleViewModel.Factory mViewModelFactory;
- private PeopleViewModel mViewModel;
@Inject
public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
@@ -52,38 +50,32 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_CANCELED);
- mViewModel = new ViewModelProvider(this, mViewModelFactory).get(PeopleViewModel.class);
+
+ PeopleViewModel viewModel = new ViewModelProvider(this, mViewModelFactory).get(
+ PeopleViewModel.class);
// Update the widget ID coming from the intent.
int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
- mViewModel.onWidgetIdChanged(widgetId);
+ viewModel.onWidgetIdChanged(widgetId);
ViewGroup view = PeopleViewBinder.create(this);
- PeopleViewBinder.bind(view, mViewModel, /* lifecycleOwner= */ this,
- () -> {
- finishActivity();
+ PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this,
+ (result) -> {
+ finishActivity(result);
return null;
});
setContentView(view);
}
- /** Finish activity with a successful widget configuration result. */
- private void finishActivity() {
- if (DEBUG) Log.d(TAG, "Widget added!");
- setActivityResult(RESULT_OK);
+ private void finishActivity(PeopleViewModel.Result result) {
+ if (result instanceof PeopleViewModel.Result.Success) {
+ if (DEBUG) Log.d(TAG, "Widget added!");
+ Intent data = ((PeopleViewModel.Result.Success) result).getData();
+ setResult(RESULT_OK, data);
+ } else {
+ if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
+ setResult(RESULT_CANCELED);
+ }
finish();
}
-
- /** Finish activity without choosing a widget. */
- public void dismissActivity(View v) {
- if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
- setResult(RESULT_CANCELED);
- finish();
- }
-
- private void setActivityResult(int result) {
- Intent resultValue = new Intent();
- resultValue.putExtra(EXTRA_APPWIDGET_ID, mViewModel.getAppWidgetId().getValue());
- setResult(result, resultValue);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index bc982cc..d8a429e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -41,7 +41,7 @@
/** A ViewBinder for [PeopleViewModel]. */
object PeopleViewBinder {
- private const val TAG = "PeopleSpaceViewBinder"
+ private const val TAG = "PeopleViewBinder"
/**
* The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
@@ -72,15 +72,15 @@
view: ViewGroup,
viewModel: PeopleViewModel,
lifecycleOwner: LifecycleOwner,
- onFinish: () -> Unit,
+ onResult: (PeopleViewModel.Result) -> Unit,
) {
- // Call [onFinish] this activity when the ViewModel tells us so.
+ // Call [onResult] as soon as a result is available.
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(CREATED) {
- viewModel.isFinished.collect { isFinished ->
- if (isFinished) {
- viewModel.clearIsFinished()
- onFinish()
+ viewModel.result.collect { result ->
+ if (result != null) {
+ viewModel.clearResult()
+ onResult(result)
}
}
}
@@ -104,7 +104,7 @@
viewModel::onTileClicked,
)
} else {
- setNoConversationsContent(view)
+ setNoConversationsContent(view, viewModel::onUserJourneyCancelled)
}
}
}
@@ -119,7 +119,7 @@
}
}
- private fun setNoConversationsContent(view: ViewGroup) {
+ private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) {
// This should never happen.
if (view.childCount > 1) {
error("view has ${view.childCount} children, it should have maximum 1")
@@ -140,6 +140,10 @@
LayoutInflater.from(context)
.inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
+ noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+ onGotItClicked()
+ }
+
// The Tile preview has colorBackground as its background. Change it so it's different than
// the activity's background.
val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
index 17de991..0834a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -16,8 +16,10 @@
package com.android.systemui.people.ui.viewmodel
+import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.Context
+import android.content.Intent
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
@@ -32,6 +34,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/**
* Models UI state for the people space, allowing the user to select which conversation should be
@@ -49,7 +52,7 @@
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
*/
private val _priorityTiles = MutableStateFlow(priorityTiles())
- val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles
+ val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
/**
* The list of the priority tiles/conversations.
@@ -58,15 +61,15 @@
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
*/
private val _recentTiles = MutableStateFlow(recentTiles())
- val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles
+ val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
/** The ID of the widget currently being edited/added. */
private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
- val appWidgetId: StateFlow<Int> = _appWidgetId
+ val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow()
- /** Whether the user journey is complete. */
- private val _isFinished = MutableStateFlow(false)
- val isFinished: StateFlow<Boolean> = _isFinished
+ /** The result of this user journey. */
+ private val _result = MutableStateFlow<Result?>(null)
+ val result: StateFlow<Result?> = _result.asStateFlow()
/** Refresh the [priorityTiles] and [recentTiles]. */
fun onTileRefreshRequested() {
@@ -79,22 +82,28 @@
_appWidgetId.value = widgetId
}
- /** Clear [isFinished], setting it to false. */
- fun clearIsFinished() {
- _isFinished.value = false
+ /** Clear [result], setting it to null. */
+ fun clearResult() {
+ _result.value = null
}
/** Called when a tile is clicked. */
fun onTileClicked(tile: PeopleTileViewModel) {
+ val widgetId = _appWidgetId.value
if (PeopleSpaceUtils.DEBUG) {
Log.d(
TAG,
- "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID: " +
- _appWidgetId.value
+ "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
)
}
- widgetRepository.setWidgetTile(_appWidgetId.value, tile.key)
- _isFinished.value = true
+ widgetRepository.setWidgetTile(widgetId, tile.key)
+ _result.value =
+ Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) })
+ }
+
+ /** Called when this user journey is cancelled. */
+ fun onUserJourneyCancelled() {
+ _result.value = Result.Cancelled
}
private fun priorityTiles(): List<PeopleTileViewModel> {
@@ -143,7 +152,12 @@
}
}
+ sealed class Result {
+ class Success(val data: Intent) : Result()
+ object Cancelled : Result()
+ }
+
companion object {
- private const val TAG = "PeopleSpaceViewModel"
+ private const val TAG = "PeopleViewModel"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index a1c66b3..4552abd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -19,8 +19,6 @@
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Secure;
@@ -28,6 +26,7 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
@@ -35,9 +34,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.PluginListener;
@@ -68,12 +65,20 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
import javax.inject.Inject;
import javax.inject.Provider;
-/** Platform implementation of the quick settings tile host **/
+/** Platform implementation of the quick settings tile host
+ *
+ * This class keeps track of the set of current tiles and is the in memory source of truth
+ * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes,
+ * {@link #onTuningChanged} will be called and the tiles will be re-created as needed.
+ *
+ * This class also provides the interface for adding/removing/changing tiles.
+ */
@SysUISingleton
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
private static final String TAG = "QSTileHost";
@@ -89,11 +94,11 @@
private final TunerService mTunerService;
private final PluginManager mPluginManager;
private final DumpManager mDumpManager;
- private final BroadcastDispatcher mBroadcastDispatcher;
private final QSLogger mQSLogger;
private final UiEventLogger mUiEventLogger;
private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
+ private final Executor mMainExecutor;
private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable
@@ -113,13 +118,11 @@
public QSTileHost(Context context,
StatusBarIconController iconController,
QSFactory defaultFactory,
- @Main Handler mainHandler,
- @Background Looper bgLooper,
+ @Main Executor mainExecutor,
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher,
Optional<CentralSurfaces> centralSurfacesOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
@@ -137,7 +140,7 @@
mDumpManager = dumpManager;
mQSLogger = qsLogger;
mUiEventLogger = uiEventLogger;
- mBroadcastDispatcher = broadcastDispatcher;
+ mMainExecutor = mainExecutor;
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
@@ -151,7 +154,7 @@
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
- mainHandler.post(() -> {
+ mainExecutor.execute(() -> {
// This is technically a hack to avoid circular dependency of
// QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
// finishes before creating any tiles.
@@ -258,6 +261,33 @@
return mTileSpecs.indexOf(spec);
}
+ /**
+ * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this
+ * will be called with the new value of the setting.
+ *
+ * This method will do the following:
+ * <ol>
+ * <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li>
+ * <li>Create new tiles for those that don't already exist. If this tiles end up being
+ * not available, they'll also be destroyed.</li>
+ * <li>Save the resolved list of tiles (current tiles that are available) into the setting.
+ * This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs},
+ * and visible tiles ({@link #mTiles}) must match.
+ * </li>
+ * </ol>
+ *
+ * Additionally, if the user has changed, it'll do the following:
+ * <ul>
+ * <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li>
+ * <li>Destroy any {@link CustomTile} and recreate it for the new user.</li>
+ * </ul>
+ *
+ * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches
+ * in main thread.
+ *
+ * @see QSTile#isAvailable
+ */
+ @MainThread
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
@@ -330,34 +360,44 @@
mCurrentUser = currentUser;
List<String> currentSpecs = new ArrayList<>(mTileSpecs);
mTileSpecs.clear();
- mTileSpecs.addAll(tileSpecs);
+ mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
mTiles.clear();
mTiles.putAll(newTiles);
if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
// If we didn't manage to create any tiles, set it to empty (default)
Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
- changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
+ changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
} else {
+ String resolvedTiles = TextUtils.join(",", mTileSpecs);
+ if (!resolvedTiles.equals(newValue)) {
+ // If the resolved tiles (those we actually ended up with) are different than
+ // the ones that are in the setting, update the Setting.
+ saveTilesToSettings(mTileSpecs);
+ }
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
}
}
+ /**
+ * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need
+ * its lifecycle terminated).
+ */
@Override
public void removeTile(String spec) {
- changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
+ mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
}
/**
* Remove many tiles at once.
*
- * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+ * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called
* multiple times).
*/
@Override
public void removeTiles(Collection<String> specs) {
- changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+ mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
}
@Override
@@ -381,26 +421,29 @@
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
- if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
- changeTileSpecs(tileSpecs -> {
- if (tileSpecs.contains(spec)) return false;
+ mMainExecutor.execute(() ->
+ changeTileSpecs(tileSpecs -> {
+ if (tileSpecs.contains(spec)) return false;
- int size = tileSpecs.size();
- if (requestPosition == POSITION_AT_END || requestPosition >= size) {
- tileSpecs.add(spec);
- } else {
- tileSpecs.add(requestPosition, spec);
- }
- return true;
- });
+ int size = tileSpecs.size();
+ if (requestPosition == POSITION_AT_END || requestPosition >= size) {
+ tileSpecs.add(spec);
+ } else {
+ tileSpecs.add(requestPosition, spec);
+ }
+ return true;
+ })
+ );
}
- void saveTilesToSettings(List<String> tileSpecs) {
+ @MainThread
+ private void saveTilesToSettings(List<String> tileSpecs) {
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
}
+ @MainThread
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
final List<String> tileSpecs = loadTileSpecs(mContext, setting);
@@ -420,29 +463,32 @@
*/
public void addTile(ComponentName tile, boolean end) {
String spec = CustomTile.toSpec(tile);
- if (!mTileSpecs.contains(spec)) {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- if (end) {
- newSpecs.add(spec);
- } else {
- newSpecs.add(0, spec);
- }
- changeTiles(mTileSpecs, newSpecs);
- }
+ addTile(spec, end ? POSITION_AT_END : 0);
}
- public void removeTile(ComponentName tile) {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- newSpecs.remove(CustomTile.toSpec(tile));
- changeTiles(mTileSpecs, newSpecs);
+ /**
+ * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
+ * removed by a <b>user action</b> like {@code adb}.
+ */
+ public void removeTileByUser(ComponentName tile) {
+ mMainExecutor.execute(() -> {
+ List<String> newSpecs = new ArrayList<>(mTileSpecs);
+ if (newSpecs.remove(CustomTile.toSpec(tile))) {
+ changeTilesByUser(mTileSpecs, newSpecs);
+ }
+ });
}
/**
* Change the tiles triggered by the user editing.
* <p>
* This is not called on device start, or on user change.
+ *
+ * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles
+ * that are removed.
*/
- public void changeTiles(List<String> previousTiles, List<String> newTiles) {
+ @MainThread
+ public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
final List<String> copy = new ArrayList<>(previousTiles);
final int NP = copy.size();
for (int i = 0; i < NP; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e52bfbd..d84b12c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -182,7 +182,7 @@
for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
newSpecs.add(mTiles.get(i).spec);
}
- host.changeTiles(mCurrentSpecs, newSpecs);
+ host.changeTilesByUser(mCurrentSpecs, newSpecs);
mCurrentSpecs = newSpecs;
}
@@ -200,7 +200,7 @@
/** */
public void resetTileSpecs(List<String> specs) {
// Notify the host so the tiles get removed callbacks.
- mHost.changeTiles(mCurrentSpecs, specs);
+ mHost.changeTilesByUser(mCurrentSpecs, specs);
setTileSpecs(specs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index bf565a8..cfc57db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -289,7 +289,7 @@
}
}
- mServices.getHost().removeTile(component);
+ mServices.getHost().removeTile(CustomTile.toSpec(component));
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 89a15f6..82de389 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -843,14 +843,20 @@
// The media player creation is slow and needs on the background thread.
return CallbackToFutureAdapter.getFuture((completer) -> {
mBgExecutor.execute(() -> {
- MediaPlayer player = MediaPlayer.create(mContext,
- Uri.fromFile(new File(mContext.getResources().getString(
- com.android.internal.R.string.config_cameraShutterSound))), null,
- new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build(), AudioSystem.newAudioSessionId());
- completer.set(player);
+ try {
+ MediaPlayer player = MediaPlayer.create(mContext,
+ Uri.fromFile(new File(mContext.getResources().getString(
+ com.android.internal.R.string.config_cameraShutterSound))),
+ null,
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build(), AudioSystem.newAudioSessionId());
+ completer.set(player);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Screenshot sound initialization failed", e);
+ completer.set(null);
+ }
});
return "ScreenshotController#loadCameraSound";
});
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index 8c8f54f..ad073c0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -49,8 +49,8 @@
) : UserFileManager, CoreStartable(context) {
companion object {
private const val FILES = "files"
- private const val SHARED_PREFS = "shared_prefs"
- internal const val ID = "UserFileManager"
+ @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ @VisibleForTesting internal const val ID = "UserFileManager"
}
private val broadcastReceiver = object : BroadcastReceiver() {
@@ -85,13 +85,15 @@
fileName
)
} else {
- Environment.buildPath(
+ val secondaryFile = Environment.buildPath(
context.filesDir,
ID,
userId.toString(),
FILES,
fileName
)
+ ensureParentDirExists(secondaryFile)
+ secondaryFile
}
}
@@ -114,6 +116,7 @@
fileName
)
+ ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
@@ -141,4 +144,18 @@
}
}
}
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ @VisibleForTesting
+ internal fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
rename to packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 905a5f9..4b71b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -60,6 +60,12 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 83ee125..aa610bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -1,20 +1,24 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.shade;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
/**
* {@link ShadeController} is an abstraction of the work that used to be hard-coded in
@@ -86,10 +90,10 @@
boolean collapsePanel();
/**
- * If {@param animate}, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
+ * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
* the panel. Post collapse runnables will be executed
*
- * @param animate
+ * @param animate true to animate the collapse, false for instantaneous collapse
*/
void collapsePanel(boolean animate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d37ecbc..f389dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.util.Log;
import android.view.ViewTreeObserver;
@@ -23,12 +23,12 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import java.util.ArrayList;
import java.util.Optional;
@@ -37,7 +37,7 @@
import dagger.Lazy;
-/** An implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
+/** An implementation of {@link ShadeController}. */
@SysUISingleton
public class ShadeControllerImpl implements ShadeController {
@@ -157,7 +157,8 @@
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (getCentralSurfaces().getNotificationShadeWindowView().isVisibleToUser()) {
+ if (getCentralSurfaces().getNotificationShadeWindowView()
+ .isVisibleToUser()) {
getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
getNotificationPanelViewController().getView().post(executable);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 9e5dab1..945bc72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,6 +68,9 @@
*/
var stableIndex: Int = -1
+ /** Access the index of the [section] or -1 if the entry does not have one */
+ val sectionIndex: Int get() = section?.index ?: -1
+
/** Copies the state of another instance. */
fun clone(other: ListAttachState) {
parent = other.parent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 075a0dc..5198d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1039,22 +1039,25 @@
NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
int sectionMemberIndex = 0;
for (int i = 0; i < notifList.size(); i++) {
- ListEntry entry = notifList.get(i);
+ final ListEntry entry = notifList.get(i);
NotifSection section = requireNonNull(entry.getSection());
if (section.getIndex() != currentSection.getIndex()) {
sectionMemberIndex = 0;
currentSection = section;
}
- entry.getAttachState().setStableIndex(sectionMemberIndex);
+ entry.getAttachState().setStableIndex(sectionMemberIndex++);
if (entry instanceof GroupEntry) {
- GroupEntry parent = (GroupEntry) entry;
- for (int j = 0; j < parent.getChildren().size(); j++) {
- entry = parent.getChildren().get(j);
- entry.getAttachState().setStableIndex(sectionMemberIndex);
- sectionMemberIndex++;
+ final GroupEntry parent = (GroupEntry) entry;
+ final NotificationEntry summary = parent.getSummary();
+ if (summary != null) {
+ summary.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
+ final List<NotificationEntry> children = parent.getChildren();
+ for (int j = 0; j < children.size(); j++) {
+ final NotificationEntry child = children.get(j);
+ child.getAttachState().setStableIndex(sectionMemberIndex++);
}
}
- sectionMemberIndex++;
}
}
@@ -1194,9 +1197,9 @@
o2.getSectionIndex());
if (cmp != 0) return cmp;
- int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
- int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
- cmp = Integer.compare(index1, index2);
+ cmp = Integer.compare(
+ getStableOrderIndex(o1),
+ getStableOrderIndex(o2));
if (cmp != 0) return cmp;
NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1210,31 +1213,32 @@
if (cmp != 0) return cmp;
}
- final NotificationEntry rep1 = o1.getRepresentativeEntry();
- final NotificationEntry rep2 = o2.getRepresentativeEntry();
- cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
+ cmp = Integer.compare(
+ o1.getRepresentativeEntry().getRanking().getRank(),
+ o2.getRepresentativeEntry().getRanking().getRank());
if (cmp != 0) return cmp;
- cmp = Long.compare(
- rep2.getSbn().getNotification().when,
- rep1.getSbn().getNotification().when);
+ cmp = -1 * Long.compare(
+ o1.getRepresentativeEntry().getSbn().getNotification().when,
+ o2.getRepresentativeEntry().getSbn().getNotification().when);
return cmp;
};
private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
- int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
- int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
- int cmp = Integer.compare(index1, index2);
+ int cmp = Integer.compare(
+ getStableOrderIndex(o1),
+ getStableOrderIndex(o2));
if (cmp != 0) return cmp;
- cmp = o1.getRepresentativeEntry().getRanking().getRank()
- - o2.getRepresentativeEntry().getRanking().getRank();
+ cmp = Integer.compare(
+ o1.getRepresentativeEntry().getRanking().getRank(),
+ o2.getRepresentativeEntry().getRanking().getRank());
if (cmp != 0) return cmp;
- cmp = Long.compare(
- o2.getRepresentativeEntry().getSbn().getNotification().when,
- o1.getRepresentativeEntry().getSbn().getNotification().when);
+ cmp = -1 * Long.compare(
+ o1.getRepresentativeEntry().getSbn().getNotification().when,
+ o2.getRepresentativeEntry().getSbn().getNotification().when);
return cmp;
};
@@ -1244,8 +1248,21 @@
*/
private boolean mForceReorderable = false;
- private boolean canReorder(ListEntry entry) {
- return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
+ private int getStableOrderIndex(ListEntry entry) {
+ if (mForceReorderable) {
+ // this is used to determine if the list is correctly sorted
+ return -1;
+ }
+ if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+ // let the stability manager constrain or allow reordering
+ return -1;
+ }
+ if (entry.getAttachState().getSectionIndex()
+ != entry.getPreviousAttachState().getSectionIndex()) {
+ // stable index is only valid within the same section; otherwise we allow reordering
+ return -1;
+ }
+ return entry.getPreviousAttachState().getStableIndex();
}
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index bf08fc7..fcf35bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.NotifPanelEventsModule;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -85,7 +86,6 @@
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.wmshell.BubblesManager;
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 94341ba..3c01802 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
@@ -1909,6 +1909,8 @@
* @param isEnabled whether the content views should be enabled for accessibility
*/
private void updateContentAccessibilityImportanceForGuts(boolean isEnabled) {
+ updateAccessibilityImportance(isEnabled);
+
if (mChildrenContainer != null) {
updateChildAccessibilityImportance(mChildrenContainer, isEnabled);
}
@@ -1924,6 +1926,15 @@
}
/**
+ * Updates whether this view is important for accessibility based on {@code isEnabled}.
+ */
+ private void updateAccessibilityImportance(boolean isEnabled) {
+ setImportantForAccessibility(isEnabled
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ /**
* Updates whether the given childView is important for accessibility based on
* {@code isEnabled}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index c2c40d8..4939a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -47,8 +47,8 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 27aa4b3..7120fe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -68,10 +68,10 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.wmshell.BubblesManager;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 8a7155a..c228af4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -55,6 +55,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -71,7 +72,6 @@
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
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 defae5b..427004e 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
@@ -84,6 +84,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -108,7 +109,6 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.util.Assert;
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 85f1689..e5835a8 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
@@ -69,6 +69,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -109,7 +110,6 @@
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 7c9df42..174bf4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -25,6 +25,7 @@
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -52,7 +53,8 @@
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
- public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600;
+ public static final int ANIMATION_DURATION_FOLD_TO_AOD =
+ AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
public static final int ANIMATION_DURATION_PULSE_APPEAR =
KeyguardSliceView.DEFAULT_ANIM_DURATION;
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 8782be5..9070ead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -428,7 +428,7 @@
if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
initSafetyTile();
} else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
- mHost.removeTile(CustomTile.getComponentFromSpec(mSafetySpec));
+ mHost.removeTile(mSafetySpec);
mHost.unmarkTileAsAutoAdded(mSafetySpec);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index a0f386f..65ba5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -60,6 +60,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -161,7 +162,7 @@
private final Handler mHandler;
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 53b5b0c..747c4de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,10 +57,9 @@
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -78,7 +77,7 @@
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
private final CentralSurfaces mCentralSurfaces;
private final Context mContext;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final NotificationPanelViewController mNotificationPanelViewController;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
@@ -91,19 +90,16 @@
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final AssistManager mAssistManager;
private final DozeServiceHost mDozeServiceHost;
- private final SysuiStatusBarStateController mStatusBarStateController;
- private final NotificationShadeWindowView mNotificationShadeWindowView;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final PowerManager mPowerManager;
private final VibratorHelper mVibratorHelper;
private final Optional<Vibrator> mVibratorOptional;
- private final LightBarController mLightBarController;
private final DisableFlagsLogger mDisableFlagsLogger;
private final int mDisplayId;
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
-
+ private final SystemBarAttributesListener mSystemBarAttributesListener;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -126,16 +122,14 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
AssistManager assistManager,
DozeServiceHost dozeServiceHost,
- SysuiStatusBarStateController statusBarStateController,
- NotificationShadeWindowView notificationShadeWindowView,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
PowerManager powerManager,
VibratorHelper vibratorHelper,
Optional<Vibrator> vibratorOptional,
- LightBarController lightBarController,
DisableFlagsLogger disableFlagsLogger,
- @DisplayId int displayId) {
+ @DisplayId int displayId,
+ SystemBarAttributesListener systemBarAttributesListener) {
mCentralSurfaces = centralSurfaces;
mContext = context;
@@ -152,20 +146,18 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mAssistManager = assistManager;
mDozeServiceHost = dozeServiceHost;
- mStatusBarStateController = statusBarStateController;
- mNotificationShadeWindowView = notificationShadeWindowView;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mPowerManager = powerManager;
mVibratorHelper = vibratorHelper;
mVibratorOptional = vibratorOptional;
- mLightBarController = lightBarController;
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
mVibratorOptional, resources);
+ mSystemBarAttributesListener = systemBarAttributesListener;
}
@Override
@@ -191,7 +183,7 @@
public void remQsTile(ComponentName tile) {
QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
if (qsPanelController != null && qsPanelController.getHost() != null) {
- qsPanelController.getHost().removeTile(tile);
+ qsPanelController.getHost().removeTileByUser(tile);
}
}
@@ -472,14 +464,18 @@
if (displayId != mDisplayId) {
return;
}
- boolean barModeChanged = mCentralSurfaces.setAppearance(appearance);
-
- mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged,
- mCentralSurfaces.getBarMode(), navbarColorManagedByIme);
-
- mCentralSurfaces.updateBubblesVisibility();
- mStatusBarStateController.setSystemBarAttributes(
- appearance, behavior, requestedVisibilities, packageName);
+ // SystemBarAttributesListener should __always__ be the top-level listener for system bar
+ // attributes changed.
+ mSystemBarAttributesListener.onSystemBarAttributesChanged(
+ displayId,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails
+ );
}
@Override
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 6cd028e..1d3b7fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -177,6 +177,7 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
@@ -485,7 +486,7 @@
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final InitController mInitController;
private final PluginDependencyProvider mPluginDependencyProvider;
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 f128a41..a7ab11c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -56,6 +56,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -121,7 +122,7 @@
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
- private final Lazy<ShadeController> mShadeController;
+ private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
private boolean mBouncerAnimating;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 451612a..374f091 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -53,6 +53,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -101,7 +102,7 @@
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final LockPatternUtils mLockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 4c23931..db8f4ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -41,6 +41,7 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -96,7 +97,7 @@
private final ScrimController mScrimController;
private final KeyguardIndicationController mKeyguardIndicationController;
private final CentralSurfaces mCentralSurfaces;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final CommandQueue mCommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 262dc83..40b9a15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -37,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
@@ -66,7 +67,7 @@
private final ActivityStarter mActivityStarter;
private final Context mContext;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private Executor mExecutor;
private final ActivityIntentHelper mActivityIntentHelper;
private final GroupExpansionManager mGroupExpansionManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
new file mode 100644
index 0000000..a0415f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.phone
+
+import android.view.InsetsVisibilities
+import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.Behavior
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Top-level listener of system attributes changed. This class is __always the first__ one to be
+ * notified about changes.
+ *
+ * It is responsible for modifying any attributes if necessary, and then notifying the other
+ * downstream listeners.
+ */
+@CentralSurfacesScope
+class SystemBarAttributesListener
+@Inject
+internal constructor(
+ private val centralSurfaces: CentralSurfaces,
+ private val featureFlags: FeatureFlags,
+ private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val lightBarController: LightBarController,
+ private val dumpManager: DumpManager,
+) : CentralSurfacesComponent.Startable, StatusBarBoundsProvider.BoundsChangeListener {
+
+ private var lastLetterboxAppearance: LetterboxAppearance? = null
+ private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null
+
+ override fun start() {
+ dumpManager.registerDumpable(javaClass.simpleName, this::dump)
+ }
+
+ override fun stop() {
+ dumpManager.unregisterDumpable(javaClass.simpleName)
+ }
+
+ override fun onStatusBarBoundsChanged() {
+ val params = lastSystemBarAttributesParams
+ if (params != null && shouldUseLetterboxAppearance(params.letterboxesArray)) {
+ onSystemBarAttributesChanged(
+ params.displayId,
+ params.appearance,
+ params.appearanceRegionsArray,
+ params.navbarColorManagedByIme,
+ params.behavior,
+ params.requestedVisibilities,
+ params.packageName,
+ params.letterboxesArray)
+ }
+ }
+
+ fun onSystemBarAttributesChanged(
+ displayId: Int,
+ @Appearance originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ navbarColorManagedByIme: Boolean,
+ @Behavior behavior: Int,
+ requestedVisibilities: InsetsVisibilities,
+ packageName: String,
+ letterboxDetails: Array<LetterboxDetails>
+ ) {
+ lastSystemBarAttributesParams =
+ SystemBarAttributesParams(
+ displayId,
+ originalAppearance,
+ originalAppearanceRegions.toList(),
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails.toList())
+
+ val (appearance, appearanceRegions) =
+ modifyAppearanceIfNeeded(
+ originalAppearance, originalAppearanceRegions, letterboxDetails)
+
+ val barModeChanged = centralSurfaces.setAppearance(appearance)
+
+ lightBarController.onStatusBarAppearanceChanged(
+ appearanceRegions, barModeChanged, centralSurfaces.barMode, navbarColorManagedByIme)
+
+ centralSurfaces.updateBubblesVisibility()
+ statusBarStateController.setSystemBarAttributes(
+ appearance, behavior, requestedVisibilities, packageName)
+ }
+
+ private fun modifyAppearanceIfNeeded(
+ appearance: Int,
+ appearanceRegions: Array<AppearanceRegion>,
+ letterboxDetails: Array<LetterboxDetails>
+ ): Pair<Int, Array<AppearanceRegion>> =
+ if (shouldUseLetterboxAppearance(letterboxDetails)) {
+ val letterboxAppearance =
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ appearance, appearanceRegions, letterboxDetails)
+ lastLetterboxAppearance = letterboxAppearance
+ Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
+ } else {
+ lastLetterboxAppearance = null
+ Pair(appearance, appearanceRegions)
+ }
+
+ private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
+ isLetterboxAppearanceFlagEnabled() && letterboxDetails.isNotEmpty()
+
+ private fun isLetterboxAppearanceFlagEnabled() =
+ featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)
+
+ private fun dump(printWriter: PrintWriter, strings: Array<String>) {
+ printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
+ printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
+ printWriter.println("letterbox appearance flag: ${isLetterboxAppearanceFlagEnabled()}")
+ }
+}
+
+/**
+ * Keeps track of the parameters passed in
+ * [SystemBarAttributesListener.onSystemBarAttributesChanged].
+ */
+private data class SystemBarAttributesParams(
+ val displayId: Int,
+ @Appearance val appearance: Int,
+ val appearanceRegions: List<AppearanceRegion>,
+ val navbarColorManagedByIme: Boolean,
+ @Behavior val behavior: Int,
+ val requestedVisibilities: InsetsVisibilities,
+ val packageName: String,
+ val letterboxes: List<LetterboxDetails>,
+) {
+ val letterboxesArray = letterboxes.toTypedArray()
+ val appearanceRegionsArray = appearanceRegions.toTypedArray()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index 590522f..d57e6a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.dagger;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import java.util.Set;
@@ -34,4 +35,9 @@
@IntoSet
CentralSurfacesComponent.Startable letterboxAppearanceCalculator(
LetterboxAppearanceCalculator letterboxAppearanceCalculator);
+
+ @Binds
+ @IntoSet
+ CentralSurfacesComponent.Startable sysBarAttrsListener(
+ SystemBarAttributesListener systemBarAttributesListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index b3bef07..c4e7a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -53,10 +53,12 @@
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.StatusIconContainer;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import com.android.systemui.statusbar.phone.TapAgainView;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
@@ -258,6 +260,11 @@
LetterboxAppearanceCalculator letterboxAppearanceCalculator
);
+ @Binds
+ @IntoSet
+ abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
+ SystemBarAttributesListener systemBarAttributesListener);
+
/**
* Creates a new {@link CollapsedStatusBarFragment}.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
new file mode 100644
index 0000000..bd6cf9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.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
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A processor that transforms raw connectivity information that we get from callbacks and turns it
+ * into a list of displayable connectivity information.
+ *
+ * This will be used for the new status bar pipeline to calculate the list of icons that should be
+ * displayed in the RHS of the status bar.
+ */
+@SysUISingleton
+class ConnectivityInfoProcessor @Inject constructor()
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
new file mode 100644
index 0000000..734bd2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+
+@Module
+class StatusBarPipelineModule {
+ @Provides
+ @SysUISingleton
+ fun provideConnectivityInfoProcessor(
+ featureFlags: FeatureFlags,
+ processorLazy: Lazy<ConnectivityInfoProcessor>
+ ): Optional<ConnectivityInfoProcessor> {
+ return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) {
+ Optional.of(processorLazy.get())
+ } else {
+ Optional.empty()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 40281a1..62fc01f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,7 +62,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -167,6 +166,7 @@
private final AtomicBoolean mGuestIsResetting;
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
+ @Nullable
private View mView;
private String mCreateSupervisedUserPackage;
private GlobalSettings mGlobalSettings;
@@ -572,9 +572,11 @@
protected void switchToUserId(int id) {
try {
- mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
- .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ if (mView != null) {
+ mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
+ .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
+ .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ }
mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
pauseRefreshUsers();
mActivityManager.switchUser(id);
@@ -936,15 +938,17 @@
guestCreationProgressDialog.show();
// userManager.createGuest will block the thread so post is needed for the dialog to show
- ThreadUtils.postOnMainThread(() -> {
+ mBgExecutor.execute(() -> {
final int guestId = createGuest();
- guestCreationProgressDialog.dismiss();
- if (guestId == UserHandle.USER_NULL) {
- Toast.makeText(mContext,
- com.android.settingslib.R.string.add_guest_failed,
- Toast.LENGTH_SHORT).show();
- }
- callback.accept(guestId);
+ mUiExecutor.execute(() -> {
+ guestCreationProgressDialog.dismiss();
+ if (guestId == UserHandle.USER_NULL) {
+ Toast.makeText(mContext,
+ com.android.settingslib.R.string.add_guest_failed,
+ Toast.LENGTH_SHORT).show();
+ }
+ callback.accept(guestId);
+ });
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index f1e89ac..aac5bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -47,6 +47,9 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -59,9 +62,6 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.ShadeControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
deleted file mode 100644
index 0686071c..0000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * A class to do the user creation process. It shows a progress dialog, and manages the user
- * creation
- */
-public class UserCreator {
-
- private final Context mContext;
- private final UserManager mUserManager;
-
- @Inject
- public UserCreator(Context context, UserManager userManager) {
- mContext = context;
- mUserManager = userManager;
- }
-
- /**
- * Shows a progress dialog then starts the user creation process on the main thread.
- *
- * @param successCallback is called when the user creation is successful.
- * @param errorCallback is called when userManager.createUser returns null.
- * (Exceptions are not handled by this class)
- */
- public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
- Runnable errorCallback) {
-
- Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
- userCreationProgressDialog.show();
-
- // userManager.createUser will block the thread so post is needed for the dialog to show
- ThreadUtils.postOnMainThread(() -> {
- UserInfo user =
- mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
- if (user == null) {
- // Couldn't create user for some reason
- userCreationProgressDialog.dismiss();
- errorCallback.run();
- return;
- }
-
- Drawable newUserIcon = userIcon;
- Resources res = mContext.getResources();
- if (newUserIcon == null) {
- newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false);
- }
- mUserManager.setUserIcon(
- user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon));
-
- userCreationProgressDialog.dismiss();
- successCallback.accept(user);
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
new file mode 100644
index 0000000..dcbbe74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.user
+
+import android.app.Dialog
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.drawable.Drawable
+import android.os.UserManager
+import com.android.internal.util.UserIcons
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * A class to do the user creation process. It shows a progress dialog, and manages the user
+ * creation
+ */
+class UserCreator @Inject constructor(
+ private val context: Context,
+ private val userManager: UserManager,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgExecutor: Executor
+) {
+ /**
+ * Shows a progress dialog then starts the user creation process on the main thread.
+ *
+ * @param successCallback is called when the user creation is successful.
+ * @param errorCallback is called when userManager.createUser returns null.
+ * (Exceptions are not handled by this class)
+ */
+ fun createUser(
+ userName: String?,
+ userIcon: Drawable?,
+ successCallback: Consumer<UserInfo?>,
+ errorCallback: Runnable
+ ) {
+ val userCreationProgressDialog: Dialog = UserCreatingDialog(context)
+ userCreationProgressDialog.show()
+
+ // userManager.createUser will block the thread so post is needed for the dialog to show
+ bgExecutor.execute {
+ val user = userManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ mainExecutor.execute main@{
+ if (user == null) {
+ // Couldn't create user for some reason
+ userCreationProgressDialog.dismiss()
+ errorCallback.run()
+ return@main
+ }
+ bgExecutor.execute {
+ var newUserIcon = userIcon
+ val res = context.resources
+ if (newUserIcon == null) {
+ newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false)
+ }
+ userManager.setUserIcon(
+ user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon))
+ }
+ userCreationProgressDialog.dismiss()
+ successCallback.accept(user)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 708a8ab..3e07144 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -54,6 +54,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -69,7 +70,6 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 5db2cf4..8b9a1e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 8717a0e..6c6f0ac 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 70025230..4dcaa7c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -89,28 +89,6 @@
}
@Test
- public void timeFormatUpdateNotifiesClockSwitchController() {
- mController.onViewAttached();
-
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
-
- mKeyguardUpdateMonitorCallbackCaptor.getValue().onTimeFormatChanged("");
- verify(mKeyguardClockSwitchController).refreshFormat();
- }
-
- @Test
- public void userChangeNotifiesClockSwitchController() {
- mController.onViewAttached();
-
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
-
- mKeyguardUpdateMonitorCallbackCaptor.getValue().onUserSwitchComplete(0);
- verify(mKeyguardClockSwitchController).refreshFormat();
- }
-
- @Test
public void setTranslationYExcludingMedia_forwardsCallToView() {
float translationY = 123f;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index 95fa3b9..f01da2d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.Paint
import android.graphics.fonts.Font
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index 3322414..ed0cd7e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
@@ -26,18 +26,17 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-
-import kotlin.math.ceil
+import org.mockito.Mockito.`when`
private val PAINT = TextPaint().apply {
textSize = 32f
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
index 603cf3b..2a183bd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.Bitmap
import android.graphics.Canvas
@@ -31,10 +31,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
import java.io.File
import kotlin.math.ceil
+import org.junit.Test
+import org.junit.runner.RunWith
private const val TEXT = "Hello, World."
private const val BIDI_TEXT = "abc\u05D0\u05D1\u05D2"
@@ -323,4 +323,4 @@
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }!!
private fun TextInterpolator.toBitmap(width: Int, height: Int) =
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }
\ No newline at end of file
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
new file mode 100644
index 0000000..1e4a9e4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.controls.management
+
+import android.content.ComponentName
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.settingslib.core.lifecycle.Lifecycle
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AppAdapterTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ private val backgroundExecutor = FakeExecutor(fakeSystemClock)
+ private val uiExecutor = FakeExecutor(fakeSystemClock)
+ @Mock lateinit var lifecycle: Lifecycle
+ @Mock lateinit var controlsListingController: ControlsListingController
+ @Mock lateinit var layoutInflater: LayoutInflater
+ @Mock lateinit var onAppSelected: (ComponentName?) -> Unit
+ @Mock lateinit var favoritesRenderer: FavoritesRenderer
+ val resources: Resources = context.resources
+ lateinit var adapter: AppAdapter
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ adapter = AppAdapter(backgroundExecutor,
+ uiExecutor,
+ lifecycle,
+ controlsListingController,
+ layoutInflater,
+ onAppSelected,
+ favoritesRenderer,
+ resources)
+ }
+
+ @Test
+ fun testOnServicesUpdated_nullLoadLabel() {
+ val captor = ArgumentCaptor
+ .forClass(ControlsListingController.ControlsListingCallback::class.java)
+ val controlsServiceInfo = mock(ControlsServiceInfo::class.java)
+ val serviceInfo = listOf(controlsServiceInfo)
+ `when`(controlsServiceInfo.loadLabel()).thenReturn(null)
+ verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
+
+ captor.value.onServicesUpdated(serviceInfo)
+ backgroundExecutor.runAllReady()
+ uiExecutor.runAllReady()
+
+ assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
+ }
+}
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
new file mode 100644
index 0000000..5191f63
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -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.dreams.complication;
+
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.controls.ControlsServiceInfo;
+import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.controller.StructureInfo;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamHomeControlsComplicationTest extends SysuiTestCase {
+ @Mock
+ private DreamHomeControlsComplication mComplication;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ControlsComponent mControlsComponent;
+
+ @Mock
+ private ControlsController mControlsController;
+
+ @Mock
+ private ControlsListingController mControlsListingController;
+
+ @Captor
+ private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getString(anyInt())).thenReturn("");
+ when(mControlsComponent.getControlsController()).thenReturn(
+ Optional.of(mControlsController));
+ when(mControlsComponent.getControlsListingController()).thenReturn(
+ Optional.of(mControlsListingController));
+ when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
+ }
+
+ @Test
+ public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(false);
+ setServiceAvailable(false);
+
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+ }
+
+ @Test
+ public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(false);
+ setServiceAvailable(true);
+
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+ }
+
+ @Test
+ public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(true);
+ setServiceAvailable(false);
+
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+ }
+
+ @Test
+ public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(true);
+ setServiceAvailable(true);
+
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
+ private void setHaveFavorites(boolean value) {
+ final List<StructureInfo> favorites = mock(List.class);
+ when(favorites.isEmpty()).thenReturn(!value);
+ when(mControlsController.getFavorites()).thenReturn(favorites);
+ }
+
+ private void setServiceAvailable(boolean value) {
+ final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+ when(serviceInfos.isEmpty()).thenReturn(!value);
+ triggerControlsListingCallback(serviceInfos);
+ }
+
+ private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
+ verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
+ mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
index 5a4bb86..df506b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -34,12 +34,12 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.AnimatableClockController;
-import com.android.keyguard.AnimatableClockView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.policy.BatteryController;
import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 8fa5c93..fe5f433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -90,6 +90,7 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -98,7 +99,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 664af75..32c66d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -32,8 +32,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -42,36 +40,23 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceRequestController;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSFactoryImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.animation.UniqueObjectHostView;
-import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -79,8 +64,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -125,34 +108,11 @@
mFragments.dispatchResume();
processAllMessages();
- QSTileHost host =
- new QSTileHost(
- mContext,
- mock(StatusBarIconController.class),
- mock(QSFactoryImpl.class),
- new Handler(),
- Looper.myLooper(),
- mock(PluginManager.class),
- mock(TunerService.class),
- () -> mock(AutoTileManager.class),
- mock(DumpManager.class),
- mock(BroadcastDispatcher.class),
- Optional.of(mock(CentralSurfaces.class)),
- mock(QSLogger.class),
- mock(UiEventLogger.class),
- mock(UserTracker.class),
- mock(SecureSettings.class),
- mock(CustomTileStatePersister.class),
- mTileServiceRequestControllerBuilder,
- mock(TileLifecycleManager.Factory.class));
-
qs.setListening(true);
processAllMessages();
qs.setListening(false);
processAllMessages();
- host.destroy();
- processAllMessages();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 8cf3fe2..7dbc561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -32,12 +32,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import androidx.annotation.Nullable;
@@ -48,7 +47,6 @@
import com.android.internal.util.CollectionUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -68,8 +66,10 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -81,18 +81,19 @@
import java.io.StringWriter;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Provider;
@RunWith(AndroidTestingRunner.class)
@SmallTest
-@RunWithLooper(setAsMainLooper = true)
public class QSTileHostTest extends SysuiTestCase {
private static String MOCK_STATE_STRING = "MockState";
private static ComponentName CUSTOM_TILE =
ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
+ private static final String SETTING = QSTileHost.TILES_SETTING;
@Mock
private StatusBarIconController mIconController;
@@ -107,8 +108,6 @@
@Mock
private DumpManager mDumpManager;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private QSTile.State mMockState;
@Mock
private CentralSurfaces mCentralSurfaces;
@@ -132,31 +131,47 @@
@Mock
private TileLifecycleManager mTileLifecycleManager;
- private Handler mHandler;
- private TestableLooper mLooper;
+ private FakeExecutor mMainExecutor;
+
private QSTileHost mQSTileHost;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLooper = TestableLooper.get(this);
- mHandler = new Handler(mLooper.getLooper());
+ mMainExecutor = new FakeExecutor(new FakeSystemClock());
+
when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
mSecureSettings = new FakeSettings();
- mSecureSettings.putStringForUser(
- QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
- mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
- mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
- mBroadcastDispatcher, mCentralSurfaces, mQSLogger, mUiEventLogger, mUserTracker,
- mSecureSettings, mCustomTileStatePersister, mTileServiceRequestControllerBuilder,
- mTileLifecycleManagerFactory);
+ saveSetting("");
+ mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
+ mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
+ mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+ mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory);
+
+ mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting()));
+ mMainExecutor.runAllReady();
+ }
+ }, mUserTracker.getUserId());
setUpTileFactory();
}
+ private void saveSetting(String value) {
+ mSecureSettings.putStringForUser(
+ SETTING, value, "", false, mUserTracker.getUserId(), false);
+ }
+
+ private String getSetting() {
+ return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId());
+ }
+
private void setUpTileFactory() {
when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
// Only create this kind of tiles
@@ -173,6 +188,10 @@
return new NotAvailableTile(mQSTileHost);
} else if (CUSTOM_TILE_SPEC.equals(spec)) {
return mCustomTile;
+ } else if ("internet".equals(spec)
+ || "wifi".equals(spec)
+ || "cell".equals(spec)) {
+ return new TestTile1(mQSTileHost);
} else {
return null;
}
@@ -196,14 +215,14 @@
public void testInvalidSpecUsesDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "not-valid");
+ saveSetting("not-valid");
assertEquals(2, mQSTileHost.getTiles().size());
}
@Test
public void testRemoveWifiAndCellularWithoutInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2");
+ saveSetting("wifi, spec1, cell, spec2");
assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
@@ -212,7 +231,7 @@
@Test
public void testRemoveWifiAndCellularWithInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet");
+ saveSetting("wifi, spec1, cell, spec2, internet");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -221,7 +240,7 @@
@Test
public void testRemoveWifiWithoutInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2");
+ saveSetting("spec1, wifi, spec2");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
@@ -230,7 +249,7 @@
@Test
public void testRemoveCellWithInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet");
+ saveSetting("spec1, spec2, cell, internet");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -239,7 +258,7 @@
@Test
public void testNoWifiNoCellularNoInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -249,7 +268,7 @@
public void testSpecWithInvalidDoesNotUseDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec2,not-valid");
+ saveSetting("spec2,not-valid");
assertEquals(1, mQSTileHost.getTiles().size());
QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
@@ -258,7 +277,7 @@
@Test
public void testDump() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
StringWriter w = new StringWriter();
PrintWriter pw = new PrintWriter(w);
mQSTileHost.dump(pw, new String[]{});
@@ -274,7 +293,7 @@
public void testDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default");
+ saveSetting("default");
assertEquals(1, mQSTileHost.getTiles().size());
QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
assertTrue(element instanceof TestTile1);
@@ -285,7 +304,7 @@
public void testNoRepeatedSpecs_addTile() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
mQSTileHost.addTile("spec1");
@@ -298,9 +317,10 @@
public void testAddTileAtValidPosition() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", 1);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -312,9 +332,10 @@
public void testAddTileAtInvalidPositionAddsToEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", 100);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -326,9 +347,10 @@
public void testAddTileAtEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -338,9 +360,10 @@
@Test
public void testNoRepeatedSpecs_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC);
+ saveSetting(CUSTOM_TILE_SPEC);
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+ mMainExecutor.runAllReady();
assertEquals(1, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -348,9 +371,10 @@
@Test
public void testAddedAtBeginningOnDefault_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -358,9 +382,10 @@
@Test
public void testAddedAtBeginning_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -368,9 +393,10 @@
@Test
public void testAddedAtEnd_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1));
@@ -409,13 +435,13 @@
@Test
public void testNotAvailableTile_specNotNull() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "na");
+ saveSetting("na");
verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
}
@Test
public void testCustomTileRemoved_stateDeleted() {
- mQSTileHost.changeTiles(List.of(CUSTOM_TILE_SPEC), List.of());
+ mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of());
verify(mCustomTileStatePersister)
.removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
@@ -423,29 +449,99 @@
@Test
public void testRemoveTiles() {
- List<String> tiles = List.of("spec1", "spec2", "spec3");
- mQSTileHost.saveTilesToSettings(tiles);
+ saveSetting("spec1,spec2,spec3");
mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+ mMainExecutor.runAllReady();
assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
}
+ @Test
+ public void testTilesRemovedInQuickSuccession() {
+ saveSetting("spec1,spec2,spec3");
+ mQSTileHost.removeTile("spec1");
+ mQSTileHost.removeTile("spec3");
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ assertEquals("spec2", getSetting());
+ }
+
+ @Test
+ public void testAddTileInMainThread() {
+ saveSetting("spec1,spec2");
+
+ mQSTileHost.addTile("spec3");
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTileInMainThread() {
+ saveSetting("spec1,spec2");
+
+ mQSTileHost.removeTile("spec1");
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTilesInMainThread() {
+ saveSetting("spec1,spec2,spec3");
+
+ mQSTileHost.removeTiles(List.of("spec3", "spec1"));
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTileByUserInMainThread() {
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
+
+ mQSTileHost.removeTileByUser(CUSTOM_TILE);
+ assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testNonValidTileNotStoredInSettings() {
+ saveSetting("spec1,not-valid");
+
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals("spec1", getSetting());
+ }
+
+ @Test
+ public void testNotAvailableTileNotStoredInSettings() {
+ saveSetting("spec1,na");
+
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals("spec1", getSetting());
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
- QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
+ QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher, CentralSurfaces centralSurfaces,
- QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker,
- SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
+ CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+ UserTracker userTracker, SecureSettings secureSettings,
+ CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory) {
- super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
- tunerService, autoTiles, dumpManager, broadcastDispatcher,
- Optional.of(centralSurfaces), qsLogger, uiEventLogger, userTracker,
- secureSettings, customTileStatePersister, tileServiceRequestControllerBuilder,
- tileLifecycleManagerFactory);
+ super(context, iconController, defaultFactory, mainExecutor, pluginManager,
+ tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
+ uiEventLogger, userTracker, secureSettings, customTileStatePersister,
+ tileServiceRequestControllerBuilder, tileLifecycleManagerFactory);
}
@Override
@@ -455,25 +551,16 @@
@Override
public void onPluginDisconnected(QSFactory plugin) {
}
-
- @Override
- void saveTilesToSettings(List<String> tileSpecs) {
- super.saveTilesToSettings(tileSpecs);
- // After tiles are changed, make sure to call onTuningChanged with the new setting if it
- // changed
- String specs = mSecureSettings.getStringForUser(
- QSTileHost.TILES_SETTING, mUserTracker.getUserId());
- onTuningChanged(TILES_SETTING, specs);
- }
}
+
private class TestTile extends QSTileImpl<QSTile.State> {
protected TestTile(QSHost host) {
super(
host,
- mLooper.getLooper(),
- new Handler(mLooper.getLooper()),
+ mock(Looper.class),
+ mock(Handler.class),
new FalsingManagerFake(),
mock(MetricsLogger.class),
mock(StatusBarStateController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 3d53062..d42cbe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -55,6 +55,6 @@
@Test
public void testResetNotifiesHost() {
mTileAdapter.resetTileSpecs(Collections.emptyList());
- verify(mQSTileHost).changeTiles(any(), any());
+ verify(mQSTileHost).changeTilesByUser(any(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 6b7e5b93..471ddfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -29,6 +29,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
@@ -65,6 +66,7 @@
import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -130,17 +132,16 @@
.thenReturn(mTileLifecycleManager);
Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
+ Executor executor = new HandlerExecutor(provider.get());
QSTileHost host = new QSTileHost(mContext,
mStatusBarIconController,
mQSFactory,
- provider.get(),
- mTestableLooper.getLooper(),
+ executor,
mPluginManager,
mTunerService,
() -> mAutoTileManager,
mDumpManager,
- mock(BroadcastDispatcher.class),
Optional.of(mCentralSurfaces),
mQSLogger,
mUiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 73226fa..6d9b01e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,6 +63,14 @@
broadcastDispatcher, backgroundExecutor)
}
+ @After
+ fun end() {
+ val dir = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID)
+ dir.deleteRecursively()
+ }
+
@Test
fun testGetFile() {
assertThat(userFileManager.getFile(TEST_FILE_NAME, 0).path)
@@ -72,8 +81,19 @@
@Test
fun testGetSharedPreferences() {
+ val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
+ val secondaryUserDir = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
+
+ assertThat(secondarySharedPref).isNotNull()
+ assertThat(secondaryUserDir.exists())
assertThat(userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 0))
- .isNotEqualTo(userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11))
+ .isNotEqualTo(secondarySharedPref)
}
@Test
@@ -115,6 +135,19 @@
verify(userManager).aliveUsers
assertThat(secondaryUserDir.exists()).isFalse()
assertThat(file.exists()).isFalse()
- dir.deleteRecursively()
+ }
+
+ @Test
+ fun testEnsureParentDirExists() {
+ val file = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ assertThat(file.parentFile.exists()).isFalse()
+ userFileManager.ensureParentDirExists(file)
+ assertThat(file.parentFile.exists()).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 1dfd7c2..ec1fa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -57,7 +57,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 665d849..fa16fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -48,7 +48,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index aaa2357..d61989f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -15,14 +15,17 @@
*/
package com.android.systemui.shared.clocks
-import org.mockito.Mockito.`when` as whenever
-import android.content.Context
import android.content.ContentResolver
+import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
@@ -35,6 +38,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@@ -45,9 +49,11 @@
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockPluginManager: PluginManager
@Mock private lateinit var mockClock: Clock
+ @Mock private lateinit var mockDefaultClock: Clock
@Mock private lateinit var mockThumbnail: Drawable
@Mock private lateinit var mockHandler: Handler
@Mock private lateinit var mockContentResolver: ContentResolver
+ private lateinit var fakeDefaultProvider: FakeClockPlugin
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
@@ -79,42 +85,52 @@
name: String,
create: () -> Clock = ::failFactory,
getThumbnail: () -> Drawable? = ::failThumbnail
- ) {
+ ): FakeClockPlugin {
metadata.add(ClockMetadata(id, name))
createCallbacks[id] = create
thumbnailCallbacks[id] = getThumbnail
+ return this
}
}
@Before
fun setUp() {
+ fakeDefaultProvider = FakeClockPlugin()
+ .addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail })
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
- registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) {
+ registry = object : ClockRegistry(
+ mockContext,
+ mockPluginManager,
+ mockHandler,
+ fakeDefaultProvider
+ ) {
override var currentClockId: ClockId
get() = settingValue
set(value) { settingValue = value }
}
- verify(mockPluginManager).addPluginListener(captor.capture(),
- eq(ClockProviderPlugin::class.java))
+
+ verify(mockPluginManager)
+ .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
pluginListener = captor.value
}
@Test
fun pluginRegistration_CorrectState() {
val plugin1 = FakeClockPlugin()
- plugin1.addClock("clock_1", "clock 1")
- plugin1.addClock("clock_2", "clock 2")
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
val plugin2 = FakeClockPlugin()
- plugin2.addClock("clock_3", "clock 3")
- plugin2.addClock("clock_4", "clock 4")
+ .addClock("clock_3", "clock 3")
+ .addClock("clock_4", "clock 4")
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
assertEquals(list, listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
ClockMetadata("clock_1", "clock 1"),
ClockMetadata("clock_2", "clock 2"),
ClockMetadata("clock_3", "clock 3"),
@@ -123,19 +139,26 @@
}
@Test
+ fun noPlugins_createDefaultClock() {
+ val clock = registry.createCurrentClock()
+ assertEquals(clock, mockDefaultClock)
+ }
+
+ @Test
fun clockIdConflict_ErrorWithoutCrash() {
val plugin1 = FakeClockPlugin()
- plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
- plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
+ .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
+ .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
val plugin2 = FakeClockPlugin()
- plugin2.addClock("clock_1", "clock 1")
- plugin2.addClock("clock_2", "clock 2")
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
assertEquals(list, listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
ClockMetadata("clock_1", "clock 1"),
ClockMetadata("clock_2", "clock 2")
))
@@ -149,13 +172,13 @@
@Test
fun createCurrentClock_pluginConnected() {
val plugin1 = FakeClockPlugin()
- plugin1.addClock("clock_1", "clock 1")
- plugin1.addClock("clock_2", "clock 2")
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
settingValue = "clock_3"
val plugin2 = FakeClockPlugin()
- plugin2.addClock("clock_3", "clock 3", { mockClock })
- plugin2.addClock("clock_4", "clock 4")
+ .addClock("clock_3", "clock 3", { mockClock })
+ .addClock("clock_4", "clock 4")
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
@@ -167,32 +190,32 @@
@Test
fun createDefaultClock_pluginDisconnected() {
val plugin1 = FakeClockPlugin()
- plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock })
- plugin1.addClock("clock_2", "clock 2")
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
settingValue = "clock_3"
val plugin2 = FakeClockPlugin()
- plugin2.addClock("clock_3", "clock 3")
- plugin2.addClock("clock_4", "clock 4")
+ .addClock("clock_3", "clock 3")
+ .addClock("clock_4", "clock 4")
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
pluginListener.onPluginDisconnected(plugin2)
val clock = registry.createCurrentClock()
- assertEquals(clock, mockClock)
+ assertEquals(clock, mockDefaultClock)
}
@Test
fun pluginRemoved_clockChanged() {
val plugin1 = FakeClockPlugin()
- plugin1.addClock("clock_1", "clock 1")
- plugin1.addClock("clock_2", "clock 2")
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
settingValue = "clock_3"
val plugin2 = FakeClockPlugin()
- plugin2.addClock("clock_3", "clock 3", { mockClock })
- plugin2.addClock("clock_4", "clock 4")
+ .addClock("clock_3", "clock 3", { mockClock })
+ .addClock("clock_4", "clock 4")
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
new file mode 100644
index 0000000..7869448
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.content.res.Resources
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+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.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultClockProviderTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+ @Mock private lateinit var mockClockView: AnimatableClockView
+ @Mock private lateinit var layoutInflater: LayoutInflater
+ @Mock private lateinit var mockClockThumbnail: Drawable
+ @Mock private lateinit var resources: Resources
+ private lateinit var provider: DefaultClockProvider
+
+ @Before
+ fun setUp() {
+ whenever(layoutInflater.inflate(R.layout.clock_default_small, null))
+ .thenReturn(mockClockView)
+ whenever(layoutInflater.inflate(R.layout.clock_default_large, null))
+ .thenReturn(mockClockView)
+ whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
+ .thenReturn(mockClockThumbnail)
+
+ provider = DefaultClockProvider(layoutInflater, resources)
+ }
+
+ @Test
+ fun providedClocks_matchesFactory() {
+ // All providers need to provide clocks & thumbnails for exposed clocks
+ for (metadata in provider.getClocks()) {
+ assertNotNull(provider.createClock(metadata.clockId))
+ assertNotNull(provider.getClockThumbnail(metadata.clockId))
+ }
+ }
+
+ @Test
+ fun defaultClock_alwaysProvided() {
+ // Default clock provider must always provide the default clock
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ assertNotNull(clock)
+ assertEquals(clock.smallClock, mockClockView)
+ assertEquals(clock.largeClock, mockClockView)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index a4ce9cd..b2ef7b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -27,6 +27,7 @@
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.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
@@ -35,7 +36,6 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index ed8b532..2cacaf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -55,7 +56,6 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.wm.shell.bubbles.Bubbles;
import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index dfa38ab..c283cec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -33,6 +33,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -1845,6 +1846,103 @@
}
@Test
+ public void stableOrderingDisregardedWithSectionChange() {
+ // GIVEN the first sectioner's packages can be changed from run-to-run
+ List<String> mutableSectionerPackages = new ArrayList<>();
+ mutableSectionerPackages.add(PACKAGE_1);
+ mListBuilder.setSectioners(asList(
+ new PackageSectioner(mutableSectionerPackages, null),
+ new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(1);
+ addNotif(1, PACKAGE_1).setRank(5);
+ addNotif(2, PACKAGE_2).setRank(2);
+ addNotif(3, PACKAGE_2).setRank(3);
+ addNotif(4, PACKAGE_3).setRank(4);
+ dispatchBuild();
+
+ // VERIFY the order and that entry reordering has not been suppressed
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3),
+ notif(4)
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the first section now claims PACKAGE_3 notifications
+ mutableSectionerPackages.add(PACKAGE_3);
+ dispatchBuild();
+
+ // VERIFY the re-sectioned notification is inserted at the top of the first section, because
+ // it's effectively "new" and "new" things are inserted at the top of their section.
+ verifyBuiltList(
+ notif(4),
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3)
+ );
+ verify(mStabilityManager).onEntryReorderSuppressed();
+ clearInvocations(mStabilityManager);
+
+ // WHEN reordering is now allowed again
+ mStabilityManager.setAllowEntryReordering(true);
+ dispatchBuild();
+
+ // VERIFY that list order changes to put the re-sectioned notification in the middle where
+ // it is ranked.
+ verifyBuiltList(
+ notif(0),
+ notif(4),
+ notif(1),
+ notif(2),
+ notif(3)
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+ }
+
+ @Test
+ public void groupRevertingToSummaryRetainsStablePosition() {
+ // GIVEN a notification group is on screen
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(2);
+ addNotif(1, PACKAGE_1).setRank(3);
+ addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+ addGroupChild(3, PACKAGE_1, "group").setRank(5);
+ addGroupChild(4, PACKAGE_1, "group").setRank(6);
+ dispatchBuild();
+
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ group(
+ summary(2),
+ child(3),
+ child(4)
+ )
+ );
+
+ // WHEN the notification summary rank increases and children removed
+ setNewRank(notif(2).entry, 1);
+ mEntrySet.remove(4);
+ mEntrySet.remove(3);
+ dispatchBuild();
+
+ // VERIFY the summary stays in the same location on rebuild
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2)
+ );
+ }
+
+ @Test
public void testStableChildOrdering() {
// WHEN the list is originally built with reordering disabled
mStabilityManager.setAllowEntryReordering(false);
@@ -2040,6 +2138,7 @@
private void assertOrder(String visible, String active, String expected) {
StringBuilder differenceSb = new StringBuilder();
+ NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
for (char c : active.toCharArray()) {
if (visible.indexOf(c) < 0) differenceSb.append(c);
}
@@ -2048,6 +2147,7 @@
for (int i = 0; i < visible.length(); i++) {
addNotif(i, String.valueOf(visible.charAt(i)))
.setRank(active.indexOf(visible.charAt(i)))
+ .setSection(section)
.setStableIndex(i);
}
@@ -2055,6 +2155,7 @@
for (int i = 0; i < difference.length(); i++) {
addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
.setRank(active.indexOf(difference.charAt(i)))
+ .setSection(section)
.setStableIndex(-1);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index 214ba16..64d0256 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -98,7 +98,7 @@
@Test
fun testPrepareDialogForApp_onlyDefaultChannel() {
- group.channels = listOf(channelDefault)
+ group.addChannel(channelDefault)
controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
setOf(channelDefault), appIcon, clickListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index bc54bf8..922e93d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -16,27 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.view.DragEvent.ACTION_DRAG_STARTED;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.DragEvent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -49,6 +28,24 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index dc6d744..381d72f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -76,10 +76,10 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e9d8f58..e26c583 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -72,6 +72,7 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -83,7 +84,6 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
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 c199147..9bcea10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -75,7 +76,6 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
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 b83743c..37a48937 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
@@ -59,6 +59,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
@@ -72,7 +73,6 @@
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 371119c..4ccbc6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -490,7 +490,7 @@
mAutoTileManager.init();
when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
- verify(mQsTileHost, times(1)).removeTile(safetyComponent);
+ verify(mQsTileHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 272ef3d..d2680eb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d79f336..57fb976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -22,26 +22,29 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.StatusBarManager;
import android.os.PowerManager;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
+import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -60,6 +63,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
+
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ShadeController mShadeController;
@Mock private CommandQueue mCommandQueue;
@@ -74,14 +78,12 @@
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private AssistManager mAssistManager;
@Mock private DozeServiceHost mDozeServiceHost;
- @Mock private StatusBarStateControllerImpl mStatusBarStateController;
- @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private PowerManager mPowerManager;
@Mock private VibratorHelper mVibratorHelper;
@Mock private Vibrator mVibrator;
- @Mock private LightBarController mLightBarController;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+ @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -106,16 +108,14 @@
mStatusBarKeyguardViewManager,
mAssistManager,
mDozeServiceHost,
- mStatusBarStateController,
- mNotificationShadeWindowView,
mNotificationStackScrollLayoutController,
mStatusBarHideIconsForBouncerManager,
mPowerManager,
mVibratorHelper,
Optional.of(mVibrator),
- mLightBarController,
new DisableFlagsLogger(),
- DEFAULT_DISPLAY);
+ DEFAULT_DISPLAY,
+ mSystemBarAttributesListener);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
@@ -170,5 +170,59 @@
verify(mDozeServiceHost).setAlwaysOnSuppressed(false);
}
+ @Test
+ public void onSystemBarAttributesChanged_forwardsToSysBarAttrsListener() {
+ int displayId = DEFAULT_DISPLAY;
+ int appearance = 123;
+ AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
+ boolean navbarColorManagedByIme = true;
+ int behavior = 456;
+ InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ String packageName = "test package name";
+ LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
+ mSbcqCallbacks.onSystemBarAttributesChanged(
+ displayId,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails);
+
+ verify(mSystemBarAttributesListener).onSystemBarAttributesChanged(
+ displayId,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails
+ );
+ }
+
+ @Test
+ public void onSystemBarAttributesChanged_differentDisplayId_doesNotForwardToAttrsListener() {
+ int appearance = 123;
+ AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
+ boolean navbarColorManagedByIme = true;
+ int behavior = 456;
+ InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ String packageName = "test package name";
+ LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
+
+ mSbcqCallbacks.onSystemBarAttributesChanged(
+ DEFAULT_DISPLAY + 1,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails);
+
+ verifyZeroInteractions(mSystemBarAttributesListener);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c6fb0ce..30c40b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -109,6 +109,8 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
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 79fce82..1046dbc 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
@@ -48,6 +48,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 7046150..c896c0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index fdb2977..43c6fe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -42,6 +42,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 1fc1473..23b1404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -32,6 +32,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -56,7 +57,7 @@
public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
@Mock private NotificationEntryManager mEntryManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private ShadeController mShadeController;
+ @Mock private com.android.systemui.shade.ShadeController mShadeController;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private SysuiStatusBarStateController mStatusBarStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
new file mode 100644
index 0000000..fa7b259
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -0,0 +1,250 @@
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.InsetsVisibilities
+import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
+import android.view.WindowInsetsController.Appearance
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SystemBarAttributesListenerTest : SysuiTestCase() {
+
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var lightBarController: LightBarController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
+
+ private lateinit var sysBarAttrsListener: SystemBarAttributesListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ anyInt(), anyObject(), anyObject()))
+ .thenReturn(TEST_LETTERBOX_APPEARANCE)
+
+ sysBarAttrsListener =
+ SystemBarAttributesListener(
+ centralSurfaces,
+ featureFlags,
+ letterboxAppearanceCalculator,
+ statusBarStateController,
+ lightBarController,
+ dumpManager)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_forwardsAppearanceToCentralSurfaces() {
+ val appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
+
+ changeSysBarAttrs(appearance)
+
+ verify(centralSurfaces).setAppearance(appearance)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToCentralSurfaces() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
+
+ verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
+
+ verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_forwardsAppearanceToStatusBarStateController() {
+ changeSysBarAttrs(TEST_APPEARANCE)
+
+ verify(statusBarStateController)
+ .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
+
+ verify(statusBarStateController)
+ .setSystemBarAttributes(
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_forwardsAppearanceToLightBarController() {
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS)
+
+ verify(lightBarController)
+ .onStatusBarAppearanceChanged(
+ eq(TEST_APPEARANCE_REGIONS), anyBoolean(), anyInt(), anyBoolean())
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToLightBarController() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+
+ verify(lightBarController)
+ .onStatusBarAppearanceChanged(
+ eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean())
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToStatusBarStateController() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verify(statusBarStateController)
+ .setSystemBarAttributes(
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verify(lightBarController)
+ .onStatusBarAppearanceChanged(
+ eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean())
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_flagFalse_doesNothing() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(false)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
+ }
+
+ private fun changeSysBarAttrs(@Appearance appearance: Int) {
+ changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
+ }
+
+ private fun changeSysBarAttrs(
+ @Appearance appearance: Int,
+ letterboxDetails: Array<LetterboxDetails>
+ ) {
+ changeSysBarAttrs(appearance, arrayOf(), letterboxDetails)
+ }
+
+ private fun changeSysBarAttrs(
+ @Appearance appearance: Int,
+ appearanceRegions: Array<AppearanceRegion>
+ ) {
+ changeSysBarAttrs(appearance, appearanceRegions, arrayOf())
+ }
+
+ private fun changeSysBarAttrs(
+ @Appearance appearance: Int,
+ appearanceRegions: Array<AppearanceRegion>,
+ letterboxDetails: Array<LetterboxDetails>
+ ) {
+ sysBarAttrsListener.onSystemBarAttributesChanged(
+ Display.DEFAULT_DISPLAY,
+ appearance,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false,
+ WindowInsetsController.BEHAVIOR_DEFAULT,
+ InsetsVisibilities(),
+ "package name",
+ letterboxDetails)
+ }
+
+ companion object {
+ private const val TEST_APPEARANCE =
+ APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
+ private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, Rect(0, 0, 150, 300))
+ private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
+ private val TEST_LETTERBOX_DETAILS =
+ arrayOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds= */ Rect(0, 0, 0, 0),
+ /* letterboxFullBounds= */ Rect(0, 0, 0, 0),
+ /* appAppearance= */ 0))
+ private val TEST_LETTERBOX_APPEARANCE =
+ LetterboxAppearance(/* appearance= */ APPEARANCE_LOW_PROFILE_BARS, arrayOf())
+ }
+}
+
+private fun <T> anyObject(): T {
+ return Mockito.anyObject<T>()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 0e25d24..a7ff59c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,12 +51,12 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
-import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 09d7c03..359a780 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -77,6 +77,7 @@
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -269,6 +270,8 @@
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -294,6 +297,8 @@
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(dialogShower).dismiss()
}
@@ -584,4 +589,24 @@
broadcastReceiverCaptor.value.onReceive(context, intent)
verify(cb).onUserSwitched()
}
+
+ @Test
+ fun onUserItemClicked_guest_runsOnBgThread() {
+ val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
+ val guestUserRecord = UserSwitcherController.UserRecord(
+ null,
+ picture,
+ true /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
+
+ userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
+ assertTrue(bgExecutor.numPending() > 0)
+ verify(userManager, never()).createGuest(context)
+ bgExecutor.runAllReady()
+ verify(userManager).createGuest(context)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
new file mode 100644
index 0000000..a85ae7df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.user
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class UserCreatorTest : SysuiTestCase() {
+ companion object {
+ const val USER_NAME = "abc"
+ }
+
+ @Mock
+ private lateinit var userCreator: UserCreator
+ @Mock
+ private lateinit var userManager: UserManager
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var bgExecutor: FakeExecutor
+ private lateinit var user: UserInfo
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ bgExecutor = FakeExecutor(FakeSystemClock())
+ userCreator = UserCreator(context, userManager, mainExecutor, bgExecutor)
+ user = Mockito.mock(UserInfo::class.java)
+ Mockito.`when`(userManager.createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0))
+ .thenReturn(user)
+ }
+
+ @Test
+ fun testCreateUser_threadingOrder() {
+ val successCallback = Mockito.mock(Consumer::class.java)
+ val errorCallback = Mockito.mock(Runnable::class.java)
+
+ userCreator.createUser(
+ USER_NAME,
+ null,
+ successCallback as Consumer<UserInfo?>,
+ errorCallback)
+
+ verify(userManager, never()).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ bgExecutor.runAllReady()
+ verify(successCallback, never()).accept(user)
+ mainExecutor.runAllReady()
+ verify(userManager, never()).setUserIcon(anyInt(), any(Bitmap::class.java))
+ bgExecutor.runAllReady()
+
+ verify(userManager).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ verify(userManager).setUserIcon(anyInt(), any(Bitmap::class.java))
+ verify(successCallback).accept(user)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9866013..59a9a3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -85,7 +85,9 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
@@ -105,9 +107,7 @@
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 6076eb1..7a09ce7 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2790,13 +2790,6 @@
if (isOnSystemDeviceIdleAllowlist(uid)) {
return REASON_SYSTEM_ALLOW_LISTED;
}
- if (isOnDeviceIdleAllowlist(uid)) {
- return REASON_ALLOWLISTED_PACKAGE;
- }
- final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
- if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
- return REASON_COMPANION_DEVICE_MANAGER;
- }
if (UserManager.isDeviceInDemoMode(mContext)) {
return REASON_DEVICE_DEMO_MODE;
}
@@ -2805,6 +2798,7 @@
.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)) {
return REASON_DISALLOW_APPS_CONTROL;
}
+ final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
if (am.isDeviceOwner(uid)) {
return REASON_DEVICE_OWNER;
}
@@ -2822,14 +2816,9 @@
final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ // Check each packages to see if any of them is in the "fixed" exemption cases.
for (String pkg : packages) {
- if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_VPN;
- } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_PLATFORM_VPN;
- } else if (isSystemModule(pkg)) {
+ if (isSystemModule(pkg)) {
return REASON_SYSTEM_MODULE;
} else if (isCarrierApp(pkg)) {
return REASON_CARRIER_PRIVILEGED_APP;
@@ -2843,6 +2832,16 @@
return REASON_ACTIVE_DEVICE_ADMIN;
}
}
+ // Loop the packages again, and check the user-configurable exemptions.
+ for (String pkg : packages) {
+ if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_VPN;
+ } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_PLATFORM_VPN;
+ }
+ }
}
if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
return REASON_ROLE_DIALER;
@@ -2850,6 +2849,12 @@
if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) {
return REASON_ROLE_EMERGENCY;
}
+ if (isOnDeviceIdleAllowlist(uid)) {
+ return REASON_ALLOWLISTED_PACKAGE;
+ }
+ if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
+ return REASON_COMPANION_DEVICE_MANAGER;
+ }
return REASON_DENIED;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3a869f8..a44a658 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -175,6 +175,7 @@
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
import com.android.server.pm.UserManagerInternal;
@@ -3584,7 +3585,8 @@
+ "), do not change associated stream volume");
continue;
}
- setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
+ setStreamVolume(groupedStream, index, flags, /*device*/ null,
+ callingPackage, callingPackage,
attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/);
}
}
@@ -3627,15 +3629,73 @@
return AudioSystem.getMinVolumeIndexForAttributes(attr);
}
+ /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
+ * Part of service interface, check permissions and parameters here */
+ public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
+ @NonNull String callingPackage, @Nullable String attributionTag) {
+ enforceModifyAudioRoutingPermission();
+ Objects.requireNonNull(vi);
+ Objects.requireNonNull(ada);
+ Objects.requireNonNull(callingPackage);
+ if (!vi.hasStreamType()) {
+ Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
+ return;
+ }
+ int index = vi.getVolumeIndex();
+ if (index == VolumeInfo.INDEX_NOT_SET) {
+ throw new IllegalArgumentException("changing device volume requires a volume index");
+ }
+
+ if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
+ || vi.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
+ // assume index meant to be in stream type range, validate
+ if ((index * 10) < mStreamStates[vi.getStreamType()].getMinIndex()
+ || (index * 10) > mStreamStates[vi.getStreamType()].getMaxIndex()) {
+ throw new IllegalArgumentException("invalid volume index " + index
+ + " not between min/max for stream " + vi.getStreamType());
+ }
+ } else {
+ // check if index needs to be rescaled
+ final int min = (mStreamStates[vi.getStreamType()].getMinIndex() + 5) / 10;
+ final int max = (mStreamStates[vi.getStreamType()].getMaxIndex() + 5) / 10;
+ if (vi.getMinVolumeIndex() != min || vi.getMaxVolumeIndex() != max) {
+ index = rescaleIndex(index,
+ /*srcMin*/ vi.getMinVolumeIndex(), /*srcMax*/ vi.getMaxVolumeIndex(),
+ /*dstMin*/ min, /*dstMax*/ max);
+ }
+ }
+ setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
+ ada, callingPackage, attributionTag);
+ }
+
/** Retain API for unsupported app usage */
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
- setStreamVolumeWithAttribution(streamType, index, flags, callingPackage, null);
+ setStreamVolumeWithAttribution(streamType, index, flags,
+ callingPackage, /*attributionTag*/ null);
}
/** @see AudioManager#setStreamVolume(int, int, int)
* Part of service interface, check permissions here */
public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
String callingPackage, String attributionTag) {
+ setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
+ callingPackage, attributionTag);
+ }
+
+ /**
+ * Internal method for a stream type volume change. Can be used to change the volume on a
+ * given device only
+ * @param streamType the stream type whose volume is to be changed
+ * @param index the volume index
+ * @param flags options for volume handling
+ * @param device null when controlling volume for the current routing, otherwise the device
+ * for which volume is being changed
+ * @param callingPackage client side-provided package name of caller, not to be trusted
+ * @param attributionTag client side-provided attribution name, not to be trusted
+ */
+ protected void setStreamVolumeWithAttributionInt(int streamType, int index, int flags,
+ @Nullable AudioDeviceAttributes device,
+ String callingPackage, String attributionTag) {
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
+ " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage);
@@ -3658,10 +3718,14 @@
return;
}
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
- index/*val1*/, flags/*val2*/, callingPackage));
- setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
- attributionTag, Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
+ final AudioEventLogger.Event event = (device == null)
+ ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ index/*val1*/, flags/*val2*/, callingPackage)
+ : new DeviceVolumeEvent(streamType, index, device, callingPackage);
+ sVolumeLogger.log(event);
+ setStreamVolume(streamType, index, flags, device,
+ callingPackage, callingPackage, attributionTag,
+ Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
}
/** @see AudioManager#isUltrasoundSupported() */
@@ -3900,11 +3964,13 @@
}
}
- private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
- String caller, String attributionTag, int uid,
+ private void setStreamVolume(int streamType, int index, int flags,
+ @Nullable AudioDeviceAttributes ada,
+ String callingPackage, String caller, String attributionTag, int uid,
boolean hasModifyAudioSettings) {
if (DEBUG_VOL) {
Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
+ + ", dev=" + ada
+ ", calling=" + callingPackage + ")");
}
if (mUseFixedVolume) {
@@ -3915,7 +3981,9 @@
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
- final int device = getDeviceForStream(streamType);
+ final int device = (ada == null)
+ ? getDeviceForStream(streamType)
+ : ada.getInternalType();
int oldIndex;
// skip a2dp absolute volume control request when the device
@@ -5419,7 +5487,8 @@
throw new SecurityException("Should only be called from system process");
}
- setStreamVolume(streamType, index, flags, packageName, packageName, null, uid,
+ setStreamVolume(streamType, index, flags, /*device*/ null,
+ packageName, packageName, null, uid,
hasAudioSettingsPermission(uid, pid));
}
@@ -7562,7 +7631,7 @@
&& !isFullyMuted()) {
index = 1;
}
- AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
+ mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
// must be called while synchronized VolumeStreamState.class
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 3225274..b5835ce 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,7 +16,9 @@
package com.android.server.audio;
+import android.annotation.NonNull;
import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaMetrics;
@@ -145,6 +147,43 @@
}
}
+ static final class DeviceVolumeEvent extends AudioEventLogger.Event {
+ final int mStream;
+ final int mVolIndex;
+ final String mDeviceNativeType;
+ final String mDeviceAddress;
+ final String mCaller;
+
+ DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
+ String callingPackage) {
+ mStream = streamType;
+ mVolIndex = index;
+ mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType());
+ mDeviceAddress = device.getAddress();
+ mCaller = callingPackage;
+ // log metrics
+ new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
+ .set(MediaMetrics.Property.EVENT, "setDeviceVolume")
+ .set(MediaMetrics.Property.STREAM_TYPE,
+ AudioSystem.streamToString(mStream))
+ .set(MediaMetrics.Property.INDEX, mVolIndex)
+ .set(MediaMetrics.Property.DEVICE, mDeviceNativeType)
+ .set(MediaMetrics.Property.ADDRESS, mDeviceAddress)
+ .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+ .record();
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("setDeviceVolume(stream:")
+ .append(AudioSystem.streamToString(mStream))
+ .append(" index:").append(mVolIndex)
+ .append(" device:").append(mDeviceNativeType)
+ .append(" addr:").append(mDeviceAddress)
+ .append(") from ").append(mCaller).toString();
+ }
+ }
+
final static class VolumeEvent extends AudioEventLogger.Event {
static final int VOL_ADJUST_SUGG_VOL = 0;
static final int VOL_ADJUST_STREAM_VOL = 1;
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 758a4ec..c3754eb 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -387,6 +387,17 @@
}
/**
+ * Same as {@link AudioSystem#setStreamVolumeIndexAS(int, int, int)}
+ * @param stream
+ * @param index
+ * @param device
+ * @return
+ */
+ public int setStreamVolumeIndexAS(int stream, int index, int device) {
+ return AudioSystem.setStreamVolumeIndexAS(stream, index, device);
+ }
+
+ /**
* Same as {@link AudioSystem#setPhoneState(int, int)}
* @param state
* @param uid
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index cd5960f..1def72b 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -46,6 +46,8 @@
import android.util.Pair;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -103,10 +105,6 @@
AudioDeviceInfo.TYPE_BLE_BROADCAST
};
- private static final int[] WIRELESS_SPEAKER_TYPES = {
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- };
-
// Spatializer state machine
private static final int STATE_UNINITIALIZED = 0;
private static final int STATE_NOT_SUPPORTED = 1;
@@ -166,6 +164,7 @@
* List of devices where Spatial Audio is possible. Each device can be enabled or disabled
* (== user choice to use or not)
*/
+ @GuardedBy("this")
private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0);
//------------------------------------------------------
@@ -520,30 +519,30 @@
* set to true if the device is added to the list, otherwise, if already
* present, the setting is left untouched.
*/
+ @GuardedBy("this")
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
boolean forceEnable) {
if (!isDeviceCompatibleWithSpatializationModes(ada)) {
return;
}
loglogi("addCompatibleAudioDevice: dev=" + ada);
- boolean isInList = false;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
SADeviceState deviceUpdated = null; // non-null on update.
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- isInList = true;
- if (forceEnable) {
- deviceState.mEnabled = true;
- deviceUpdated = deviceState;
- }
- break;
+ if (deviceState != null) {
+ if (forceEnable && !deviceState.mEnabled) {
+ deviceUpdated = deviceState;
+ deviceUpdated.mEnabled = true;
}
- }
- if (!isInList) {
- final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
- deviceState.mEnabled = true;
- mSADevices.add(deviceState);
- deviceUpdated = deviceState;
+ } else {
+ // When adding, force the device type to be a canonical one.
+ final int canonicalDeviceType = getCanonicalDeviceType(ada.getType());
+ if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
+ Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes "
+ + ada);
+ return;
+ }
+ deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress());
+ mSADevices.add(deviceUpdated);
}
if (deviceUpdated != null) {
onRoutingUpdated();
@@ -574,90 +573,95 @@
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
- SADeviceState deviceUpdated = null; // non-null on update.
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- deviceState.mEnabled = false;
- deviceUpdated = deviceState;
- break;
- }
- }
- if (deviceUpdated != null) {
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState != null && deviceState.mEnabled) {
+ deviceState.mEnabled = false;
onRoutingUpdated();
mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceUpdated, "removeCompatibleAudioDevice");
+ logDeviceState(deviceState, "removeCompatibleAudioDevice");
}
}
/**
+ * Returns a possibly aliased device type which is used
+ * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist).
+ */
+ private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) {
+ if (isWireless(deviceType)) return deviceType;
+
+ final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+ if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+ return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+ return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
+ }
+ return AudioDeviceInfo.TYPE_UNKNOWN;
+ }
+
+ /**
+ * Returns the Spatial Audio device state for an audio device attributes
+ * or null if it does not exist.
+ */
+ @GuardedBy("this")
+ @Nullable
+ private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ final int deviceType = ada.getType();
+ final boolean isWireless = isWireless(deviceType);
+ final int canonicalDeviceType = getCanonicalDeviceType(deviceType);
+
+ for (SADeviceState deviceState : mSADevices) {
+ if (deviceState.mDeviceType == canonicalDeviceType
+ && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ return deviceState;
+ }
+ }
+ return null;
+ }
+
+ /**
* Return if Spatial Audio is enabled and available for the given device
* @param ada
* @return a pair of boolean, 1/ enabled? 2/ available?
*/
private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) {
- // if not a wireless device, this value will be overwritten to map the type
- // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES
- @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
-
- // if not a wireless device: find if media device is in the speaker, wired headphones
- if (!isWireless(deviceType)) {
- // is the device type capable of doing SA?
- if (!mSACapableDeviceTypes.contains(deviceType)) {
- Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
- return new Pair<>(false, false);
- }
- // what spatialization mode to use for this device?
- final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
- if (spatMode == Integer.MIN_VALUE) {
- // error case, device not found
- Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
- return new Pair<>(false, false);
- }
- // map the spatialization mode to the SPEAKER or HEADPHONES device
- if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
- deviceType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
- } else {
- deviceType = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
- }
- } else { // wireless device
- if (isWirelessSpeaker(deviceType) && !mTransauralSupported) {
- Log.i(TAG, "Device incompatible with Spatial Audio (no transaural) dev:"
- + ada);
- return new Pair<>(false, false);
- }
- if (!mBinauralSupported) {
- Log.i(TAG, "Device incompatible with Spatial Audio (no binaural) dev:"
- + ada);
- return new Pair<>(false, false);
- }
+ final @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
+ // is the device type capable of doing SA?
+ if (!mSACapableDeviceTypes.contains(deviceType)) {
+ Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
+ return new Pair<>(false, false);
}
-
- boolean enabled = false;
- boolean available = false;
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- available = true;
- enabled = deviceState.mEnabled;
- break;
- }
+ // what spatialization mode to use for this device?
+ final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+ if (spatMode == Integer.MIN_VALUE) {
+ // error case, device not found
+ Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
+ return new Pair<>(false, false);
}
- return new Pair<>(enabled, available);
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState == null) {
+ // no matching device state?
+ Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada);
+ return new Pair<>(false, false);
+ }
+ // found the matching device state.
+ return new Pair<>(deviceState.mEnabled, true /* available */);
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
if (!isDeviceCompatibleWithSpatializationModes(ada)) {
return;
}
- boolean knownDevice = false;
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- knownDevice = true;
- break;
+ if (findDeviceStateForAudioDeviceAttributes(ada) == null) {
+ // wireless device types should be canonical, but we translate to be sure.
+ final int canonicalDeviceType = getCanonicalDeviceType((ada.getType()));
+ if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
+ Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes "
+ + ada);
+ return;
}
- }
- if (!knownDevice) {
- final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
+ final SADeviceState deviceState =
+ new SADeviceState(canonicalDeviceType, ada.getAddress());
mSADevices.add(deviceState);
mAudioService.persistSpatialAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
@@ -699,12 +703,7 @@
if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
return false;
}
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- return true;
- }
- }
- return false;
+ return findDeviceStateForAudioDeviceAttributes(ada) != null;
}
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
@@ -1086,20 +1085,18 @@
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- if (!deviceState.mHasHeadTracker) {
- Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
- + " device:" + ada + " on a device without headtracker");
- return;
- }
- Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
- deviceState.mHeadTrackerEnabled = enabled;
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceState, "setHeadTrackerEnabled");
- break;
- }
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState == null) return;
+ if (!deviceState.mHasHeadTracker) {
+ Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
+ + " device:" + ada + " on a device without headtracker");
+ return;
}
+ Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
+ deviceState.mHeadTrackerEnabled = enabled;
+ mAudioService.persistSpatialAudioDeviceSettings();
+ logDeviceState(deviceState, "setHeadTrackerEnabled");
+
// check current routing to see if it affects the headtracking mode
if (ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
@@ -1113,12 +1110,8 @@
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- return deviceState.mHasHeadTracker;
- }
- }
- return false;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ return deviceState != null && deviceState.mHasHeadTracker;
}
/**
@@ -1131,15 +1124,14 @@
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- if (!deviceState.mHasHeadTracker) {
- deviceState.mHasHeadTracker = true;
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceState, "setHasHeadTracker");
- }
- return deviceState.mHeadTrackerEnabled;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState != null) {
+ if (!deviceState.mHasHeadTracker) {
+ deviceState.mHasHeadTracker = true;
+ mAudioService.persistSpatialAudioDeviceSettings();
+ logDeviceState(deviceState, "setHasHeadTracker");
}
+ return deviceState.mHeadTrackerEnabled;
}
Log.e(TAG, "setHasHeadTracker: device not found for:" + ada);
return false;
@@ -1150,15 +1142,9 @@
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.matchesAudioDeviceAttributes(ada)) {
- if (!deviceState.mHasHeadTracker) {
- return false;
- }
- return deviceState.mHeadTrackerEnabled;
- }
- }
- return false;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ return deviceState != null
+ && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled;
}
synchronized boolean isHeadTrackerAvailable() {
@@ -1582,12 +1568,6 @@
mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
}
- public boolean matchesAudioDeviceAttributes(AudioDeviceAttributes ada) {
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
- return (deviceType == mDeviceType)
- && (!wireless || ada.getAddress().equals(mDeviceAddress));
- }
}
/*package*/ synchronized String getSADeviceSettings() {
@@ -1608,7 +1588,10 @@
// small list, not worth overhead of Arrays.stream(devSettings)
for (String setting : devSettings) {
SADeviceState devState = SADeviceState.fromPersistedString(setting);
+ // Note if the device is not compatible with spatialization mode
+ // or the device type is not canonical, it is ignored.
if (devState != null
+ && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType)
&& isDeviceCompatibleWithSpatializationModes(
devState.getAudioDeviceAttributes())) {
mSADevices.add(devState);
@@ -1645,15 +1628,6 @@
return false;
}
- private static boolean isWirelessSpeaker(@AudioDeviceInfo.AudioDeviceType int deviceType) {
- for (int type : WIRELESS_SPEAKER_TYPES) {
- if (type == deviceType) {
- return true;
- }
- }
- return false;
- }
-
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 1b24aa8..0d789f7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -108,6 +108,17 @@
}
}
+ @Override
+ public void onBiometricAction(@BiometricStateListener.Action int action) {
+ for (IBiometricStateListener listener : mBiometricStateListeners) {
+ try {
+ listener.onBiometricAction(action);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in onBiometricAction", e);
+ }
+ }
+ }
+
/**
* This should be invoked when:
* 1) Enrolled --> None-enrolled
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
index 8ea4ee9..4417f92 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
@@ -31,6 +31,11 @@
default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {}
/**
+ * Invoked when a biometric action has occurred.
+ */
+ default void onBiometricAction(int action) {}
+
+ /**
* Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
* (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
* revokeChallenge) so that a scheduler can process ClientMonitors regardless of their
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
index b82f5fa..07041bf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
@@ -44,6 +44,13 @@
}
@Override
+ public final void onBiometricAction(int action) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onBiometricAction(action);
+ }
+ }
+
+ @Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 0c5b19b..94b67ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1020,9 +1020,18 @@
@Override
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
FingerprintService.this.registerBiometricStateListener(listener);
}
- }
+
+ @Override
+ public void onPowerPressed() {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ for (ServiceProvider provider : mServiceProviders) {
+ provider.onPowerPressed();
+ }
+ }
+ };
public FingerprintService(Context context) {
super(context);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java
new file mode 100644
index 0000000..288c372
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+/**
+ * Interface for handling power presses.
+ */
+public interface PowerPressHandler {
+ /**
+ * Indicates a power press has occurred.
+ */
+ void onPowerPressed();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 9cdbdc9..24a47e0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -150,6 +150,8 @@
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
+ void onPowerPressed();
+
/**
* Sets side-fps controller
* @param controller side-fps controller
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 79e3bf5..e1626f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -21,6 +21,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -29,10 +30,15 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Build;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -46,17 +52,18 @@
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.SensorOverlays;
+import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import java.util.ArrayList;
import java.util.function.Supplier;
/**
- * Fingerprint-specific authentication client supporting the
- * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
+ * Fingerprint-specific authentication client supporting the {@link
+ * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements
- Udfps, LockoutConsumer {
+class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
+ implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
@NonNull private final LockoutCache mLockoutCache;
@@ -64,33 +71,88 @@
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
- @Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable
+ private ICancellationSignal mCancellationSignal;
private boolean mIsPointerDown;
+ private final Handler mHandler;
- FingerprintAuthenticationClient(@NonNull Context context,
+ private static final int MESSAGE_IGNORE_AUTH = 1;
+ private static final int MESSAGE_AUTH_SUCCESS = 2;
+ private long mWaitForAuthKeyguard;
+ private long mWaitForAuthBp;
+ private long mIgnoreAuthFor;
+
+ FingerprintAuthenticationClient(
+ @NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
- boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
+ @NonNull IBinder token,
+ long requestId,
+ @NonNull ClientMonitorCallbackConverter listener,
+ int targetUserId,
+ long operationId,
+ boolean restricted,
+ @NonNull String owner,
+ int cookie,
+ boolean requireConfirmation,
int sensorId,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ @NonNull BiometricLogger biometricLogger,
+ @NonNull BiometricContext biometricContext,
boolean isStrongBiometric,
- @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
+ @Nullable TaskStackListener taskStackListener,
+ @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
- @NonNull FingerprintSensorPropertiesInternal sensorProps) {
- super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
- cookie, requireConfirmation, sensorId,
- biometricLogger, biometricContext,
- isStrongBiometric, taskStackListener,
- lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+ @NonNull FingerprintSensorPropertiesInternal sensorProps,
+ @NonNull Handler handler) {
+ super(
+ context,
+ lazyDaemon,
+ token,
+ listener,
+ targetUserId,
+ operationId,
+ restricted,
+ owner,
+ cookie,
+ requireConfirmation,
+ sensorId,
+ biometricLogger,
+ biometricContext,
+ isStrongBiometric,
+ taskStackListener,
+ lockoutCache,
+ allowBackgroundAuthentication,
+ true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+ mHandler = handler;
+
+ mWaitForAuthKeyguard =
+ context.getResources()
+ .getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow);
+ mWaitForAuthBp =
+ context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow);
+ mIgnoreAuthFor =
+ context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime);
+
+ if (mSensorProps.isAnySidefpsType()) {
+ if (Build.isDebuggable()) {
+ mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
+ (int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT);
+ mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp,
+ UserHandle.USER_CURRENT);
+ mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor,
+ UserHandle.USER_CURRENT);
+ }
+ }
}
@Override
@@ -108,8 +170,8 @@
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(mALSProbeCallback,
- getBiometricContextUnsubscriber(), callback);
+ return new ClientMonitorCompositeCallback(
+ mALSProbeCallback, getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -126,16 +188,37 @@
}
@Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> token) {
- super.onAuthenticated(identifier, authenticated, token);
+ public void onAuthenticated(
+ BiometricAuthenticator.Identifier identifier,
+ boolean authenticated,
+ ArrayList<Byte> token) {
- if (authenticated) {
- mState = STATE_STOPPED;
- mSensorOverlays.hide(getSensorId());
- } else {
- mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ long delay = 0;
+ if (authenticated && mSensorProps.isAnySidefpsType()) {
+ if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
+ Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ return;
+ }
+ delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
+ Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power until: " + delay);
}
+
+ mHandler.postDelayed(
+ () -> {
+ if (authenticated && mSensorProps.isAnySidefpsType()) {
+ Slog.i(TAG, "(sideFPS): No power press detected, sending auth");
+ }
+ super.onAuthenticated(identifier, authenticated, token);
+ if (authenticated) {
+ mState = STATE_STOPPED;
+ mSensorOverlays.hide(getSensorId());
+ } else {
+ mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ }
+ },
+ MESSAGE_AUTH_SUCCESS,
+ delay);
}
@Override
@@ -165,7 +248,8 @@
mCancellationSignal = doAuthenticate();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ onError(
+ BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
@@ -177,15 +261,18 @@
if (session.hasContextMethods()) {
final OperationContext opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().authenticateWithContext(
- mOperationId, opContext);
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
+ final ICancellationSignal cancel =
+ session.getSession().authenticateWithContext(mOperationId, opContext);
+ getBiometricContext()
+ .subscribe(
+ opContext,
+ ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
return cancel;
} else {
return session.getSession().authenticate(mOperationId);
@@ -202,7 +289,8 @@
mCancellationSignal.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ onError(
+ BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
}
@@ -284,8 +372,13 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
- getLogger().logOnError(getContext(), getOperationContext(),
- error, 0 /* vendorCode */, getTargetUserId());
+ getLogger()
+ .logOnError(
+ getContext(),
+ getOperationContext(),
+ error,
+ 0 /* vendorCode */,
+ getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -303,8 +396,13 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
- getLogger().logOnError(getContext(), getOperationContext(),
- error, 0 /* vendorCode */, getTargetUserId());
+ getLogger()
+ .logOnError(
+ getContext(),
+ getOperationContext(),
+ error,
+ 0 /* vendorCode */,
+ getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -315,4 +413,19 @@
mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
+
+ @Override
+ public void onPowerPressed() {
+ if (mSensorProps.isAnySidefpsType()) {
+ Slog.i(TAG, "(sideFPS): onPowerPressed");
+ if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
+ Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ }
+ mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
+ mHandler.postDelayed(() -> {}, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index f23659c..f4f0a19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -139,7 +140,7 @@
controller.onEnrollmentHelp(getSensorId());
}
});
-
+ mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
super.onAcquired(acquiredInfo, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index f16af78..6f6c09b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -67,6 +67,7 @@
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -388,6 +389,11 @@
}
@Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
@@ -441,7 +447,7 @@
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
- mSensors.get(sensorId).getSensorProperties());
+ mSensors.get(sensorId).getSensorProperties(), mHandler);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -614,6 +620,21 @@
}
@Override
+ public void onPowerPressed() {
+ for (int i = 0; i < mSensors.size(); i++) {
+ final Sensor sensor = mSensors.valueAt(i);
+ BaseClientMonitor client = sensor.getScheduler().getCurrentClient();
+ if (client == null) {
+ return;
+ }
+ if (!(client instanceof PowerPressHandler)) {
+ continue;
+ }
+ ((PowerPressHandler) client).onPowerPressed();
+ }
+ }
+
+ @Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
mSidefpsController = controller;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 2a3f34a..c1a8638 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -603,6 +603,11 @@
}
@Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
@@ -821,6 +826,11 @@
}
@Override
+ public void onPowerPressed() {
+ Slog.e(TAG, "onPowerPressed not supported for HIDL clients");
+ }
+
+ @Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
mUdfpsOverlayController = controller;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 1d478e5..2a59c8c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -151,6 +152,8 @@
controller.onEnrollmentHelp(getSensorId());
}
});
+
+ mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
}
@Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 076ac2b..6f9a176 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -43,6 +43,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -63,6 +64,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.SafetyProtectionUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -1181,9 +1183,19 @@
String message =
getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
Slog.i(TAG, message);
- Toast.makeText(
- getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT)
- .show();
+ Toast toastToShow;
+ if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
+ Drawable safetyProtectionIcon = getContext()
+ .getDrawable(R.drawable.ic_safety_protection);
+ toastToShow = Toast.makeCustomToastWithIcon(getContext(),
+ UiThread.get().getLooper(), message,
+ Toast.LENGTH_SHORT, safetyProtectionIcon);
+ } else {
+ toastToShow = Toast.makeText(
+ getContext(), UiThread.get().getLooper(), message,
+ Toast.LENGTH_SHORT);
+ }
+ toastToShow.show();
} catch (PackageManager.NameNotFoundException e) {
// do nothing
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index f526960..86b8d32 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3281,7 +3281,7 @@
// All the above failures are configuration errors, and are terminal
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
ikeException.getErrorType(),
@@ -3299,7 +3299,7 @@
// All the above failures are configuration errors, and are terminal
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
VpnManager.ERROR_CLASS_RECOVERABLE,
ikeException.getErrorType(),
@@ -3318,7 +3318,7 @@
} else if (exception instanceof IkeNetworkLostException) {
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST,
@@ -3333,7 +3333,7 @@
if (exception.getCause() instanceof UnknownHostException) {
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
@@ -3347,7 +3347,7 @@
} else if (exception.getCause() instanceof IkeTimeoutException) {
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
@@ -3361,7 +3361,7 @@
} else if (exception.getCause() instanceof IOException) {
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_IO,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 9fb1d8e..625f193 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -871,10 +871,9 @@
if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
mClientManager.unregisterClient(mHostEndPointId);
mRegistered = false;
+ mAppOpsManager.stopWatchingMode(this);
+ mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId);
}
- mAppOpsManager.stopWatchingMode(this);
-
- mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId);
}
private String authStateToString(@ContextHubManager.AuthorizationState int state) {
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index a9b2570..7fe2f81 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -16,15 +16,8 @@
package com.android.server.notification;
-import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
import android.os.Handler;
import android.util.AtomicFile;
import android.util.Slog;
@@ -60,18 +53,9 @@
private static final String TAG = "NotiHistoryDatabase";
private static final boolean DEBUG = NotificationManagerService.DBG;
private static final int HISTORY_RETENTION_DAYS = 1;
- private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000;
private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20;
private static final long INVALID_FILE_TIME_MS = -1;
- private static final String ACTION_HISTORY_DELETION =
- NotificationHistoryDatabase.class.getSimpleName() + ".CLEANUP";
- private static final int REQUEST_CODE_DELETION = 1;
- private static final String SCHEME_DELETION = "delete";
- private static final String EXTRA_KEY = "key";
-
- private final Context mContext;
- private final AlarmManager mAlarmManager;
private final Object mLock = new Object();
private final Handler mFileWriteHandler;
@VisibleForTesting
@@ -87,9 +71,7 @@
@VisibleForTesting
NotificationHistory mBuffer;
- public NotificationHistoryDatabase(Context context, Handler fileWriteHandler, File dir) {
- mContext = context;
- mAlarmManager = context.getSystemService(AlarmManager.class);
+ public NotificationHistoryDatabase(Handler fileWriteHandler, File dir) {
mCurrentVersion = DEFAULT_CURRENT_VERSION;
mFileWriteHandler = fileWriteHandler;
mVersionFile = new File(dir, "version");
@@ -97,11 +79,6 @@
mHistoryFiles = new LinkedList<>();
mBuffer = new NotificationHistory();
mWriteBufferRunnable = new WriteBufferRunnable();
-
- IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION);
- deletionFilter.addDataScheme(SCHEME_DELETION);
- mContext.registerReceiver(mFileCleanupReceiver, deletionFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
}
public void init() {
@@ -117,7 +94,7 @@
checkVersionAndBuildLocked();
indexFilesLocked();
- prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
+ prune();
}
}
@@ -246,7 +223,14 @@
}
/**
- * Remove any files that are too old and schedule jobs to clean up the rest
+ * Remove any files that are too old.
+ */
+ void prune() {
+ prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
+ }
+
+ /**
+ * Remove any files that are too old.
*/
void prune(final int retentionDays, final long currentTimeMillis) {
synchronized (mLock) {
@@ -265,10 +249,6 @@
if (creationTime <= retentionBoundary.getTimeInMillis()) {
deleteFile(currentOldestFile);
- } else {
- // all remaining files are newer than the cut off; schedule jobs to delete
- scheduleDeletion(
- currentOldestFile.getBaseFile(), creationTime, retentionDays);
}
}
}
@@ -306,26 +286,6 @@
removeFilePathFromHistory(file.getBaseFile().getAbsolutePath());
}
- private void scheduleDeletion(File file, long creationTime, int retentionDays) {
- final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS);
- scheduleDeletion(file, deletionTime);
- }
-
- private void scheduleDeletion(File file, long deletionTime) {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling deletion for " + file.getName() + " at " + deletionTime);
- }
- final PendingIntent pi = PendingIntent.getBroadcast(mContext,
- REQUEST_CODE_DELETION,
- new Intent(ACTION_HISTORY_DELETION)
- .setData(new Uri.Builder().scheme(SCHEME_DELETION)
- .appendPath(file.getAbsolutePath()).build())
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_KEY, file.getAbsolutePath()),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, deletionTime, pi);
- }
-
private void writeLocked(AtomicFile file, NotificationHistory notifications)
throws IOException {
FileOutputStream fos = file.startWrite();
@@ -355,12 +315,6 @@
}
}
- public void unregisterFileCleanupReceiver() {
- if(mContext != null) {
- mContext.unregisterReceiver(mFileCleanupReceiver);
- }
- }
-
private static long safeParseLong(String fileName) {
// AtomicFile will create copies of the numeric files with ".new" and ".bak"
// over the course of its processing. If these files still exist on boot we need to clean
@@ -372,40 +326,15 @@
}
}
- private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action == null) {
- return;
- }
- if (ACTION_HISTORY_DELETION.equals(action)) {
- try {
- synchronized (mLock) {
- final String filePath = intent.getStringExtra(EXTRA_KEY);
- AtomicFile fileToDelete = new AtomicFile(new File(filePath));
- if (DEBUG) {
- Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName());
- }
- fileToDelete.delete();
- removeFilePathFromHistory(filePath);
- }
- } catch (Exception e) {
- Slog.e(TAG, "Failed to delete notification history file", e);
- }
- }
- }
- };
-
final class WriteBufferRunnable implements Runnable {
@Override
public void run() {
long time = System.currentTimeMillis();
- run(time, new AtomicFile(new File(mHistoryDir, String.valueOf(time))));
+ run(new AtomicFile(new File(mHistoryDir, String.valueOf(time))));
}
- void run(long time, AtomicFile file) {
+ void run(AtomicFile file) {
synchronized (mLock) {
if (DEBUG) Slog.d(TAG, "WriteBufferRunnable "
+ file.getBaseFile().getAbsolutePath());
@@ -413,8 +342,6 @@
writeLocked(file, mBuffer);
mHistoryFiles.addFirst(file);
mBuffer = new NotificationHistory();
-
- scheduleDeletion(file.getBaseFile(), time, HISTORY_RETENTION_DAYS);
} catch (IOException e) {
Slog.e(TAG, "Failed to write buffer to disk. not flushing buffer", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java
index d9e0d79..0d975cc 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java
@@ -35,6 +35,6 @@
if(sTestingNotificationHistoryDb != null) {
return sTestingNotificationHistoryDb;
}
- return new NotificationHistoryDatabase(context, handler, rootDir);
+ return new NotificationHistoryDatabase(handler, rootDir);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryJobService.java b/services/core/java/com/android/server/notification/NotificationHistoryJobService.java
new file mode 100644
index 0000000..3776ad7
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationHistoryJobService.java
@@ -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.server.notification;
+
+import static android.app.job.JobScheduler.RESULT_SUCCESS;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This service runs every twenty minutes to ensure the retention policy for notification history
+ * data.
+ */
+public class NotificationHistoryJobService extends JobService {
+ private final static String TAG = "NotificationHistoryJob";
+ private static final long JOB_RUN_INTERVAL = TimeUnit.MINUTES.toMillis(20);
+
+ static final int BASE_JOB_ID = 237039804;
+
+ static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler.getPendingJob(BASE_JOB_ID) == null) {
+ ComponentName component =
+ new ComponentName(context, NotificationHistoryJobService.class);
+ JobInfo newJob = new JobInfo.Builder(BASE_JOB_ID, component)
+ .setRequiresDeviceIdle(false)
+ .setPeriodic(JOB_RUN_INTERVAL)
+ .build();
+ if (jobScheduler.schedule(newJob) != RESULT_SUCCESS) {
+ Slog.w(TAG, "Failed to schedule history cleanup job");
+ }
+ }
+ }
+
+ private CancellationSignal mSignal;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ mSignal = new CancellationSignal();
+ new Thread(() -> {
+ NotificationManagerInternal nmInternal =
+ LocalServices.getService(NotificationManagerInternal.class);
+ nmInternal.cleanupHistoryFiles();
+ jobFinished(params, mSignal.isCanceled());
+ }).start();
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (mSignal != null) {
+ mSignal.cancel();
+ }
+ return false;
+ }
+}
+
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 0aacd13..6a46048 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -84,6 +84,11 @@
}
void onBootPhaseAppsCanStart() {
+ try {
+ NotificationHistoryJobService.scheduleJob(mContext);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failed to schedule cleanup job", e);
+ }
mSettingsObserver.observe();
}
@@ -151,6 +156,24 @@
}
}
+ public void cleanupHistoryFiles() {
+ synchronized (mLock) {
+ int n = mUserUnlockedStates.size();
+ for (int i = 0; i < n; i++) {
+ // cleanup old files for currently unlocked users. User are additionally cleaned
+ // on unlock in NotificationHistoryDatabase.init().
+ if (mUserUnlockedStates.valueAt(i)) {
+ final NotificationHistoryDatabase userHistory =
+ mUserState.get(mUserUnlockedStates.keyAt(i));
+ if (userHistory == null) {
+ continue;
+ }
+ userHistory.prune();
+ }
+ }
+ }
+ }
+
public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
synchronized (mLock) {
int userId = UserHandle.getUserId(uid);
@@ -288,7 +311,6 @@
private void disableHistory(NotificationHistoryDatabase userHistory, @UserIdInt int userId) {
userHistory.disableHistory();
- userHistory.unregisterFileCleanupReceiver();
mUserPendingHistoryDisables.put(userId, false);
mHistoryEnabled.put(userId, false);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 8a62736..bc38856 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -45,4 +45,6 @@
/** Send a notification to the user prompting them to review their notification permissions. */
void sendReviewPermissionsNotification();
+
+ void cleanupHistoryFiles();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e589080..793f592d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2733,7 +2733,7 @@
}
@Override
- public void onUserUnlocking(@NonNull TargetUser user) {
+ public void onUserUnlocked(@NonNull TargetUser user) {
mHandler.post(() -> {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryUnlockUser");
try {
@@ -6345,6 +6345,12 @@
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
}
+
+ @Override
+ public void cleanupHistoryFiles() {
+ checkCallerIsSystem();
+ mHistoryManager.cleanupHistoryFiles();
+ }
};
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -6758,9 +6764,8 @@
protected void doChannelWarningToast(int forUid, CharSequence toastText) {
Binder.withCleanCallingIdentity(() -> {
- final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0;
final boolean warningEnabled = Settings.Global.getInt(getContext().getContentResolver(),
- Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, defaultWarningEnabled) != 0;
+ Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 0) != 0;
if (warningEnabled) {
Toast toast = Toast.makeText(getContext(), mHandler.getLooper(), toastText,
Toast.LENGTH_SHORT);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 066692d..477b8da 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -86,7 +86,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -1328,17 +1327,16 @@
return null;
}
NotificationChannelGroup group = r.groups.get(groupId).clone();
- ArrayList channels = new ArrayList();
+ group.setChannels(new ArrayList<>());
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (groupId.equals(nc.getGroup())) {
- channels.add(nc);
+ group.addChannel(nc);
}
}
}
- group.setChannels(channels);
return group;
}
}
@@ -1359,48 +1357,44 @@
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Objects.requireNonNull(pkg);
- List<NotificationChannelGroup> groups = new ArrayList<>();
+ Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
synchronized (mPackagePreferences) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
}
- Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap();
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (nc.getGroup() != null) {
if (r.groups.get(nc.getGroup()) != null) {
- ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
- nc.getGroup(), new ArrayList<>());
- channels.add(nc);
- groupedChannels.put(nc.getGroup(), channels);
+ NotificationChannelGroup ncg = groups.get(nc.getGroup());
+ if (ncg == null) {
+ ncg = r.groups.get(nc.getGroup()).clone();
+ ncg.setChannels(new ArrayList<>());
+ groups.put(nc.getGroup(), ncg);
+
+ }
+ ncg.addChannel(nc);
}
} else {
- ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
- null, new ArrayList<>());
- channels.add(nc);
- groupedChannels.put(null, channels);
+ nonGrouped.addChannel(nc);
}
}
}
- for (NotificationChannelGroup group : r.groups.values()) {
- ArrayList<NotificationChannel> channels =
- groupedChannels.getOrDefault(group.getId(), new ArrayList<>());
- if (includeEmpty || !channels.isEmpty()) {
- NotificationChannelGroup clone = group.clone();
- clone.setChannels(channels);
- groups.add(clone);
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
+ groups.put(null, nonGrouped);
+ }
+ if (includeEmpty) {
+ for (NotificationChannelGroup group : r.groups.values()) {
+ if (!groups.containsKey(group.getId())) {
+ groups.put(group.getId(), group);
+ }
}
}
-
- if (includeNonGrouped && groupedChannels.containsKey(null)) {
- NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
- nonGrouped.setChannels(groupedChannels.get(null));
- groups.add(nonGrouped);
- }
- return new ParceledListSlice<>(groups);
+ return new ParceledListSlice<>(new ArrayList<>(groups.values()));
}
}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index f6b22bc..a03a16a 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -113,7 +113,7 @@
mScanFlags = scanFlags;
}
mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
- mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+ mSystemScanFlags = mScanFlags | SCAN_AS_SYSTEM;
mExecutorService = ParallelPackageParser.makeExecutorService();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 94e8ec5..85b0149 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1836,8 +1836,6 @@
mAppDataHelper = new AppDataHelper(this);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
- mInjector.getSystemPartitions());
mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
mAppDataHelper);
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
@@ -1958,6 +1956,9 @@
+ ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
}
+ mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
+ mInjector.getSystemPartitions());
+
// when upgrading from pre-M, promote system app permissions from install to runtime
mPromoteSystemApps =
mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 60864a3..dfb4f24 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -219,38 +219,21 @@
// Install/uninstall system packages per user.
for (int userId : mUm.getUserIds()) {
- final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
+ final Set<String> userAllowlist = getInstallablePackagesForUserId(userId);
- // If null, run for all packages
- if (userWhitelist == null) {
- pmInt.forEachPackageState(packageState -> {
- if (packageState.getPkg() == null) {
- return;
- }
- final boolean install = !packageState.getTransientState()
- .isHiddenUntilInstalled();
- if (packageState.getUserStateOrDefault(userId).isInstalled() != install
- && shouldChangeInstallationState(packageState, install, userId,
- isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
- changesToCommit.add(userId, packageState.getPackageName(), install);
- }
- });
- } else {
- for (String packageName : userWhitelist) {
- PackageStateInternal packageState = pmInt.getPackageStateInternal(packageName);
- if (packageState.getPkg() == null) {
- continue;
- }
-
- final boolean install = !packageState.getTransientState()
- .isHiddenUntilInstalled();
- if (packageState.getUserStateOrDefault(userId).isInstalled() != install
- && shouldChangeInstallationState(packageState, install, userId,
- isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
- changesToCommit.add(userId, packageState.getPackageName(), install);
- }
+ pmInt.forEachPackageState(packageState -> {
+ if (packageState.getPkg() == null) {
+ return;
}
- }
+ boolean install = (userAllowlist == null
+ || userAllowlist.contains(packageState.getPackageName()))
+ && !packageState.getTransientState().isHiddenUntilInstalled();
+ if (packageState.getUserStateOrDefault(userId).isInstalled() != install
+ && shouldChangeInstallationState(packageState, install, userId,
+ isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
+ changesToCommit.add(userId, packageState.getPackageName(), install);
+ }
+ });
}
pmInt.commitPackageStateMutation(null, packageStateMutator -> {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d88949b..d645bb2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -949,6 +949,11 @@
}
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
+ // SideFPS still needs to know about suppressed power buttons, in case it needs to block
+ // an auth attempt.
+ if (count == 1) {
+ mSideFpsEventHandler.notifyPowerPressed();
+ }
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
Slog.i(TAG, "Suppressed redundant power key press while "
+ "already in the process of turning the screen on.");
@@ -968,7 +973,7 @@
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
} else if (count == 1 && interactive && !beganFromNonInteractive) {
- if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
+ if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
return;
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 41d0272..af2b902 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -19,6 +19,7 @@
import static android.hardware.biometrics.BiometricStateListener.STATE_BP_AUTH;
import static android.hardware.biometrics.BiometricStateListener.STATE_ENROLLING;
import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE;
+import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,8 +35,12 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
import android.view.WindowManager;
import com.android.internal.R;
@@ -46,14 +51,26 @@
import java.util.function.Supplier;
/**
- * Defines behavior for handling interactions between power button events and
- * fingerprint-related operations, for devices where the fingerprint sensor (side fps)
- * lives on the power button.
+ * Defines behavior for handling interactions between power button events and fingerprint-related
+ * operations, for devices where the fingerprint sensor (side fps) lives on the power button.
*/
public class SideFpsEventHandler {
private static final int DEBOUNCE_DELAY_MILLIS = 500;
+ private int getTapWaitForPowerDuration(Context context) {
+ int tap = context.getResources().getInteger(
+ R.integer.config_sidefpsEnrollPowerPressWindow);
+ if (Build.isDebuggable()) {
+ tap = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap,
+ UserHandle.USER_CURRENT);
+ }
+ return tap;
+ }
+
+ private static final String TAG = "SideFpsEventHandler";
+
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
@@ -61,20 +78,26 @@
@NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
@Nullable private Dialog mDialog;
- @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener = (dialog) -> {
- if (mDialog == dialog) {
- mDialog = null;
- }
- };
+ private final Runnable mTurnOffDialog =
+ () -> {
+ dismissDialog("mTurnOffDialog");
+ };
+
+ @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener;
private @BiometricStateListener.State int mBiometricState;
+ private final int mTapWaitForPowerDuration;
+ private FingerprintManager mFingerprintManager;
SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
}
@VisibleForTesting
- SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager,
+ SideFpsEventHandler(
+ Context context,
+ Handler handler,
+ PowerManager powerManager,
Supplier<AlertDialog.Builder> dialogSupplier) {
mContext = context;
mHandler = handler;
@@ -82,91 +105,131 @@
mDialogSupplier = dialogSupplier;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
+ mDialogDismissListener =
+ (dialog) -> {
+ if (mDialog == dialog) {
+ if (mHandler != null) {
+ mHandler.removeCallbacks(mTurnOffDialog);
+ }
+ mDialog = null;
+ }
+ };
// ensure dialog is dismissed if screen goes off for unrelated reasons
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- }
- }, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ context.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ }
+ },
+ new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ mTapWaitForPowerDuration = getTapWaitForPowerDuration(context);
}
/**
- * Called from {@link PhoneWindowManager} after the power button is pressed and displays a
- * dialog confirming the user's intent to turn screen off if a fingerprint operation is
- * active. The device goes to sleep if confirmed otherwise the dialog is dismissed.
+ * Called from {@link PhoneWindowManager} to notify FingerprintManager that a single tap power
+ * button has been pressed.
+ */
+ public void notifyPowerPressed() {
+ Log.i(TAG, "notifyPowerPressed");
+ if (mFingerprintManager == null) {
+ mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ }
+ if (mFingerprintManager == null) {
+ return;
+ }
+ mFingerprintManager.onPowerPressed();
+ }
+
+ /**
+ * Called from {@link PhoneWindowManager} and will dictate if the SideFpsEventHandler should
+ * handle the power press.
*
* @param eventTime powerPress event time
* @return true if powerPress was consumed, false otherwise
*/
- public boolean onSinglePressDetected(long eventTime) {
+ public boolean shouldConsumeSinglePress(long eventTime) {
if (!mSideFpsEventHandlerReady.get()) {
return false;
}
switch (mBiometricState) {
case STATE_ENROLLING:
- case STATE_BP_AUTH:
- mHandler.post(() -> {
- if (mDialog != null) {
- mDialog.dismiss();
- }
- mDialog = showConfirmDialog(mDialogSupplier.get(),
- mPowerManager, eventTime, mBiometricState, mDialogDismissListener);
- });
+ mHandler.post(
+ () -> {
+ if (mHandler.hasCallbacks(mTurnOffDialog)) {
+ Log.v(TAG, "Detected a tap to turn off dialog, ignoring");
+ mHandler.removeCallbacks(mTurnOffDialog);
+ }
+ });
+ showDialog(eventTime, "Enroll Power Press");
return true;
+ case STATE_BP_AUTH:
+ return true;
+ case STATE_KEYGUARD_AUTH:
default:
return false;
}
}
@NonNull
- private static Dialog showConfirmDialog(@NonNull AlertDialog.Builder dialogBuilder,
- @NonNull PowerManager powerManager, long eventTime,
+ private static Dialog showConfirmDialog(
+ @NonNull AlertDialog.Builder dialogBuilder,
+ @NonNull PowerManager powerManager,
+ long eventTime,
@BiometricStateListener.State int biometricState,
@NonNull DialogInterface.OnDismissListener dismissListener) {
final boolean enrolling = biometricState == STATE_ENROLLING;
- final int title = enrolling ? R.string.fp_power_button_enrollment_title
- : R.string.fp_power_button_bp_title;
- final int message = enrolling ? R.string.fp_power_button_enrollment_message
- : R.string.fp_power_button_bp_message;
- final int positiveText = enrolling ? R.string.fp_power_button_enrollment_positive_button
- : R.string.fp_power_button_bp_positive_button;
- final int negativeText = enrolling ? R.string.fp_power_button_enrollment_negative_button
- : R.string.fp_power_button_bp_negative_button;
+ final int title =
+ enrolling
+ ? R.string.fp_power_button_enrollment_title
+ : R.string.fp_power_button_bp_title;
+ final int message =
+ enrolling
+ ? R.string.fp_power_button_enrollment_message
+ : R.string.fp_power_button_bp_message;
+ final int positiveText =
+ enrolling
+ ? R.string.fp_power_button_enrollment_positive_button
+ : R.string.fp_power_button_bp_positive_button;
+ final int negativeText =
+ enrolling
+ ? R.string.fp_power_button_enrollment_negative_button
+ : R.string.fp_power_button_bp_negative_button;
- final Dialog confirmScreenOffDialog = dialogBuilder
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(positiveText,
- (dialog, which) -> {
- dialog.dismiss();
- powerManager.goToSleep(
- eventTime,
- PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
- 0 /* flags */
- );
- })
- .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
- .setOnDismissListener(dismissListener)
- .setCancelable(false)
- .create();
- confirmScreenOffDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ final Dialog confirmScreenOffDialog =
+ dialogBuilder
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(
+ positiveText,
+ (dialog, which) -> {
+ dialog.dismiss();
+ powerManager.goToSleep(
+ eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ 0 /* flags */);
+ })
+ .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
+ .setOnDismissListener(dismissListener)
+ .setCancelable(false)
+ .create();
+ confirmScreenOffDialog
+ .getWindow()
+ .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
confirmScreenOffDialog.show();
return confirmScreenOffDialog;
}
/**
- * Awaits notification from PhoneWindowManager that fingerprint service is ready
- * to send updates about power button fps sensor state. Then configures a
- * BiometricStateListener to receive and record updates to fps state, and
- * registers the BiometricStateListener in FingerprintManager.
+ * Awaits notification from PhoneWindowManager that fingerprint service is ready to send updates
+ * about power button fps sensor state. Then configures a BiometricStateListener to receive and
+ * record updates to fps state, and registers the BiometricStateListener in FingerprintManager.
*/
public void onFingerprintSensorReady() {
final PackageManager pm = mContext.getPackageManager();
@@ -184,12 +247,12 @@
if (fingerprintManager.isPowerbuttonFps()) {
fingerprintManager.registerBiometricStateListener(
new BiometricStateListener() {
- @Nullable
- private Runnable mStateRunnable = null;
+ @Nullable private Runnable mStateRunnable = null;
@Override
public void onStateChanged(
@BiometricStateListener.State int newState) {
+ Log.d(TAG, "onStateChanged : " + newState);
if (mStateRunnable != null) {
mHandler.removeCallbacks(mStateRunnable);
mStateRunnable = null;
@@ -200,16 +263,58 @@
// damper when moving to idle in case auth is first
if (newState == STATE_IDLE) {
mStateRunnable = () -> mBiometricState = newState;
- mHandler.postDelayed(mStateRunnable,
- DEBOUNCE_DELAY_MILLIS);
+ // This is also useful in the case of biometric
+ // prompt.
+ // If a user has recently succeeded/failed auth, we
+ // want to disable the power button for a short
+ // period of time (so ethey are able to view the
+ // prompt)
+ mHandler.postDelayed(
+ mStateRunnable, DEBOUNCE_DELAY_MILLIS);
+ dismissDialog("STATE_IDLE");
} else {
mBiometricState = newState;
}
}
+
+ @Override
+ public void onBiometricAction(
+ @BiometricStateListener.Action int action) {
+ Log.d(TAG, "onBiometricAction " + action);
+ switch (action) {
+ case BiometricStateListener.ACTION_SENSOR_TOUCH:
+ mHandler.postDelayed(
+ mTurnOffDialog,
+ mTapWaitForPowerDuration);
+ break;
+ }
+ }
});
mSideFpsEventHandlerReady.set(true);
}
}
});
}
+
+ private void dismissDialog(String reason) {
+ Log.d(TAG, "Dismissing dialog with reason: " + reason);
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ }
+
+ private void showDialog(long time, String reason) {
+ Log.d(TAG, "Showing dialog with reason: " + reason);
+ if (mDialog != null && mDialog.isShowing()) {
+ Log.d(TAG, "Ignoring show dialog");
+ return;
+ }
+ mDialog =
+ showConfirmDialog(
+ mDialogSupplier.get(),
+ mPowerManager,
+ time,
+ mBiometricState,
+ mDialogDismissListener);
+ }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 50d1bd6..dbf05f1 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4801,6 +4801,11 @@
.IS_STAY_ON_WHILE_PLUGGED_IN_WIRELESS,
((mStayOnWhilePluggedInSetting & BatteryManager.BATTERY_PLUGGED_WIRELESS)
!= 0));
+ proto.write(
+ PowerServiceSettingsAndConfigurationDumpProto.StayOnWhilePluggedInProto
+ .IS_STAY_ON_WHILE_PLUGGED_IN_DOCK,
+ ((mStayOnWhilePluggedInSetting & BatteryManager.BATTERY_PLUGGED_DOCK)
+ != 0));
proto.end(stayOnWhilePluggedInToken);
proto.write(
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 71b1bc2..bec3754 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1207,7 +1207,7 @@
private int mImeBackDisposition = 0;
private boolean mShowImeSwitcher = false;
private IBinder mImeToken = null;
- private LetterboxDetails[] mLetterboxDetails;
+ private LetterboxDetails[] mLetterboxDetails = new LetterboxDetails[0];
private void setBarAttributes(@Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a044e60..ed32a7d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5343,7 +5343,11 @@
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
if (!delayed) {
+ // We aren't delayed anything, but exiting windows rely on the animation finished
+ // callback being called in case the ActivityRecord was pretending to be delayed,
+ // which we might have done because we were in closing/opening apps list.
if (!usingShellTransitions) {
+ onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
if (visible) {
// The token was made immediately visible, there will be no entrance animation.
// We need to inform the client the enter animation was finished.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 890b910..fe3f37d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -56,6 +56,7 @@
import static android.os.Process.INVALID_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
@@ -82,6 +83,7 @@
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -2973,6 +2975,10 @@
newParent = candidateTf;
}
}
+ if (newParent.canHaveEmbeddingActivityTransition(mStartActivity)) {
+ // Make sure the embedded TaskFragment is included in the start activity transition.
+ newParent.collectEmbeddedTaskFragmentIfNeeded();
+ }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
@@ -3005,12 +3011,18 @@
errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
break;
}
+ case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: {
+ errMsg = "Cannot embed activity across TaskFragments for result, resultTo: "
+ + mStartActivity.resultTo;
+ break;
+ }
default:
errMsg = "Unhandled embed result:" + result;
}
if (taskFragment.isOrganized()) {
mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
+ taskFragment, HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT,
new SecurityException(errMsg));
} else {
// If the taskFragment is not organized, just dump error message as warning logs.
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 1898cc6..219092b 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -204,11 +204,8 @@
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final WindowToken token = mTargetWindowTokens.keyAt(i);
for (int j = token.getChildCount() - 1; j >= 0; j--) {
- // TODO(b/234585256): The consumer should be handleFinishDrawing(). And check why
- // the local window might easily time out.
- final WindowState w = token.getChildAt(j);
- if (w.isClientLocal()) continue;
- w.applyWithNextDraw(t -> {});
+ // TODO(b/234585256): The consumer should be handleFinishDrawing().
+ token.getChildAt(j).applyWithNextDraw(t -> {});
}
}
mIsSyncDrawRequested = true;
@@ -484,7 +481,16 @@
if (op == null) return false;
if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
if (op.mDrawTransaction == null) {
- op.mDrawTransaction = postDrawTransaction;
+ if (w.isClientLocal()) {
+ // Use a new transaction to merge the draw transaction of local window because the
+ // same instance will be cleared (Transaction#clear()) after reporting draw.
+ op.mDrawTransaction = mService.mTransactionFactory.get();
+ op.mDrawTransaction.merge(postDrawTransaction);
+ } else {
+ // The transaction read from parcel (the client is in a different process) is
+ // already a copy, so just reference it directly.
+ op.mDrawTransaction = postDrawTransaction;
+ }
} else {
op.mDrawTransaction.merge(postDrawTransaction);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f438e46..1489891 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4252,6 +4252,17 @@
void detach(Transaction t) {
removeImeSurface(t);
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("ImeScreenshot{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" imeTarget=" + mImeTarget);
+ sb.append(" surface=" + mImeSurface);
+ sb.append('}');
+ return sb.toString();
+ }
}
private void attachAndShowImeScreenshotOnTarget() {
@@ -4284,15 +4295,23 @@
}
/**
- * Removes the IME screenshot when necessary.
- *
- * Used when app transition animation finished or obsoleted screenshot surface like size
- * changed by rotation.
+ * Removes the IME screenshot when the caller is a part of the attached target window.
*/
- void removeImeScreenshotIfPossible() {
- if (mImeLayeringTarget == null
- || mImeLayeringTarget.mAttrs.type != TYPE_APPLICATION_STARTING
- && !mImeLayeringTarget.inTransitionSelfOrParent()) {
+ void removeImeSurfaceByTarget(WindowContainer win) {
+ if (mImeScreenshot == null || win == null) {
+ return;
+ }
+ // The starting window shouldn't be the input target to attach the IME screenshot during
+ // transitioning.
+ if (win.asWindowState() != null
+ && win.asWindowState().mAttrs.type == TYPE_APPLICATION_STARTING) {
+ return;
+ }
+
+ final WindowState screenshotTarget = mImeScreenshot.getImeTarget();
+ final boolean winIsOrContainsScreenshotTarget = (win == screenshotTarget
+ || win.getWindow(w -> w == screenshotTarget) != null);
+ if (winIsOrContainsScreenshotTarget) {
removeImeSurfaceImmediately();
}
}
@@ -4640,10 +4659,8 @@
wc, SurfaceAnimator.animationTypeToString(type), mImeScreenshot,
mImeScreenshot.getImeTarget());
}
- if (mImeScreenshot != null && (wc == mImeScreenshot.getImeTarget()
- || wc.getWindow(w -> w == mImeScreenshot.getImeTarget()) != null)
- && (type & WindowState.EXIT_ANIMATING_TYPES) != 0) {
- removeImeSurfaceImmediately();
+ if ((type & WindowState.EXIT_ANIMATING_TYPES) != 0) {
+ removeImeSurfaceByTarget(wc);
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 86a73c9..bf4b65d 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -125,7 +125,8 @@
mDisplayContent = displayContent;
mStateController = stateController;
mFakeControl = new InsetsSourceControl(
- source.getType(), null /* leash */, new Point(), InsetsSourceControl.INVALID_HINTS);
+ source.getType(), null /* leash */, false /* initialVisible */, new Point(),
+ Insets.NONE);
mControllable = InsetsPolicy.isInsetsTypeControllable(source.getType());
}
@@ -468,7 +469,8 @@
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
- mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition, mInsetsHint);
+ mControl = new InsetsSourceControl(mSource.getType(), leash, mClientVisible,
+ surfacePosition, mInsetsHint);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -553,7 +555,8 @@
// to the client in case that the client applies its transaction sooner than ours
// that we could unexpectedly overwrite the surface state.
return new InsetsSourceControl(mControl.getType(), null /* leash */,
- mControl.getSurfacePosition(), mControl.getInsetsHint());
+ mControl.isInitiallyVisible(), mControl.getSurfacePosition(),
+ mControl.getInsetsHint());
}
return mControl;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index dfce40b..fe91122 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -212,10 +212,6 @@
// Map from the PID to the top most app which has a focused window of the process.
final ArrayMap<Integer, ActivityRecord> mTopFocusedAppByProcess = new ArrayMap<>();
- // Only a separate transaction until we separate the apply surface changes
- // transaction from the global transaction.
- private final SurfaceControl.Transaction mDisplayTransaction;
-
// The tag for the token to put root tasks on the displays to sleep.
private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
@@ -460,7 +456,6 @@
RootWindowContainer(WindowManagerService service) {
super(service);
- mDisplayTransaction = service.mTransactionFactory.get();
mHandler = new MyHandler(service.mH.getLooper());
mService = service.mAtmService;
mTaskSupervisor = mService.mTaskSupervisor;
@@ -779,6 +774,10 @@
return leakedSurface || killedApps;
}
+ /**
+ * This method should only be called from {@link WindowSurfacePlacer}. Otherwise the recursion
+ * check and {@link WindowSurfacePlacer#isInLayout()} won't take effect.
+ */
void performSurfacePlacement() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
try {
@@ -1001,19 +1000,20 @@
mObscuringWindow = null;
// TODO(multi-display): Support these features on secondary screens.
- final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
+ final DisplayContent defaultDc = mDefaultDisplay;
final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();
final int defaultDw = defaultInfo.logicalWidth;
final int defaultDh = defaultInfo.logicalHeight;
+ final SurfaceControl.Transaction t = defaultDc.getSyncTransaction();
if (mWmService.mWatermark != null) {
- mWmService.mWatermark.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
+ mWmService.mWatermark.positionSurface(defaultDw, defaultDh, t);
}
if (mWmService.mStrictModeFlash != null) {
- mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
+ mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, t);
}
if (mWmService.mEmulatorDisplayOverlay != null) {
mWmService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh,
- mWmService.getDefaultDisplayRotation(), mDisplayTransaction);
+ defaultDc.getRotation(), t);
}
final int count = mChildren.size();
@@ -1024,8 +1024,10 @@
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
- mWmService.mDisplayManagerInternal.performTraversal(mDisplayTransaction);
- SurfaceControl.mergeToGlobalTransaction(mDisplayTransaction);
+ mWmService.mDisplayManagerInternal.performTraversal(t);
+ if (t != defaultDc.mSyncTransaction) {
+ SurfaceControl.mergeToGlobalTransaction(t);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f8a9d46..d91e2c8 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -165,6 +165,13 @@
* indicate that an Activity can't be embedded because the Activity is started on a new task.
*/
static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
+ /**
+ * An embedding check result of
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because the Activity is started on a new
+ * TaskFragment, e.g. start an Activity on a new TaskFragment for result.
+ */
+ static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4;
/**
* Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
@@ -175,6 +182,7 @@
EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
EMBEDDING_DISALLOWED_NEW_TASK,
+ EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
})
@interface EmbeddingCheckResult {}
@@ -567,9 +575,17 @@
if (!isAllowedToEmbedActivityInUntrustedMode(a)
&& !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
- } else if (smallerThanMinDimension(a)) {
+ }
+
+ if (smallerThanMinDimension(a)) {
return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
}
+
+ // Cannot embed activity across TaskFragments for activity result.
+ if (a.resultTo != null && a.resultTo.getTaskFragment() != this) {
+ return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
+ }
+
return EMBEDDING_ALLOWED;
}
@@ -2316,6 +2332,26 @@
return !startBounds.equals(getBounds());
}
+ boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
+ if (!isOrganizedTaskFragment() || !mTransitionController.isShellTransitionsEnabled()) {
+ return false;
+ }
+ // The activity should request open transition when it is becoming visible.
+ return child.isVisibleRequested();
+ }
+
+ void collectEmbeddedTaskFragmentIfNeeded() {
+ if (!isOrganizedTaskFragment() || mTransitionController.isCollecting(this)) {
+ return;
+ }
+ if (getChildCount() == 0) {
+ // The TaskFragment is new created, and just becoming non-empty.
+ mTransitionController.collectExistenceChange(this);
+ } else {
+ mTransitionController.collect(this);
+ }
+ }
+
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 392d4c2..d4551be 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.window.TaskFragmentOrganizer.putExceptionInBundle;
+import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -214,12 +214,15 @@
}
}
- void onTaskFragmentError(IBinder errorCallbackToken, Throwable exception) {
+ void onTaskFragmentError(IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
+ int opType, Throwable exception) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Sending TaskFragment error exception=%s", exception.toString());
- final Bundle exceptionBundle = putExceptionInBundle(exception);
+ final TaskFragmentInfo info =
+ taskFragment != null ? taskFragment.getTaskFragmentInfo() : null;
+ final Bundle errorBundle = putErrorInfoInBundle(exception, info, opType);
try {
- mOrganizer.onTaskFragmentError(errorCallbackToken, exceptionBundle);
+ mOrganizer.onTaskFragmentError(errorCallbackToken, errorBundle);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentError callback", e);
}
@@ -462,13 +465,15 @@
}
void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
- Throwable exception) {
+ TaskFragment taskFragment, int opType, Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
+ .setTaskFragment(taskFragment)
.setException(exception)
+ .setOpType(opType)
.build();
mPendingTaskFragmentEvents.add(pendingEvent);
// Make sure the error event will be dispatched if there are no other changes.
@@ -567,19 +572,22 @@
// Set when the event is deferred due to the host task is invisible. The defer time will
// be the last active time of the host task.
private long mDeferTime;
+ private int mOpType;
private PendingTaskFragmentEvent(@EventType int eventType,
ITaskFragmentOrganizer taskFragmentOrg,
@Nullable TaskFragment taskFragment,
@Nullable IBinder errorCallbackToken,
@Nullable Throwable exception,
- @Nullable ActivityRecord activity) {
+ @Nullable ActivityRecord activity,
+ int opType) {
mEventType = eventType;
mTaskFragmentOrg = taskFragmentOrg;
mTaskFragment = taskFragment;
mErrorCallbackToken = errorCallbackToken;
mException = exception;
mActivity = activity;
+ mOpType = opType;
}
/**
@@ -610,6 +618,7 @@
private Throwable mException;
@Nullable
private ActivityRecord mActivity;
+ private int mOpType;
Builder(@EventType int eventType, ITaskFragmentOrganizer taskFragmentOrg) {
mEventType = eventType;
@@ -636,9 +645,14 @@
return this;
}
+ Builder setOpType(int opType) {
+ mOpType = opType;
+ return this;
+ }
+
PendingTaskFragmentEvent build() {
return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment,
- mErrorCallbackToken, mException, mActivity);
+ mErrorCallbackToken, mException, mActivity, mOpType);
}
}
}
@@ -667,6 +681,10 @@
}
private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) {
+ return true;
+ }
+
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
@@ -757,7 +775,8 @@
state.onTaskFragmentParentInfoChanged(taskFragment);
break;
case PendingTaskFragmentEvent.EVENT_ERROR:
- state.onTaskFragmentError(event.mErrorCallbackToken, event.mException);
+ state.onTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType,
+ event.mException);
break;
case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
state.onActivityReparentToTask(event.mActivity);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 91f69a5..31d8eb8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1149,6 +1149,26 @@
return false;
}
+ private static boolean isTranslucent(@NonNull WindowContainer wc) {
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment != null) {
+ if (taskFragment.isTranslucent(null /* starting */)) {
+ return true;
+ }
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTaskFragment != null) {
+ // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything
+ // behind two adjacent TaskFragments are occluded.
+ return adjacentTaskFragment.isTranslucent(null /* starting */);
+ }
+ }
+ // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
+ // it is effected by child visibility but needs to work even
+ // before visibility is committed. This means refactoring some
+ // checks to use requested visibility.
+ return !wc.fillsParent();
+ }
+
/**
* Under some conditions (eg. all visible targets within a parent container are transitioning
* the same way) the transition can be "promoted" to the parent container. This means an
@@ -1701,20 +1721,13 @@
if (mShowWallpaper || wc.showWallpaper()) {
flags |= FLAG_SHOW_WALLPAPER;
}
- if (!wc.fillsParent()) {
- // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
- // it is effected by child visibility but needs to work even
- // before visibility is committed. This means refactoring some
- // checks to use requested visibility.
+ if (isTranslucent(wc)) {
flags |= FLAG_TRANSLUCENT;
}
final Task task = wc.asTask();
if (task != null && task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
- if (task != null && task.isTranslucent(null)) {
- flags |= FLAG_TRANSLUCENT;
- }
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9d6e250..f31c267 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5611,7 +5611,7 @@
mWindowsInsetsChanged = 0;
// We need to update resizing windows and dispatch the new insets state
// to them.
- mRoot.performSurfacePlacement();
+ mWindowPlacerLocked.performSurfacePlacement();
}
}
break;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ee64354..0f5655c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -764,7 +765,7 @@
bottomActivity)) {
Slog.w(TAG, "Skip removing TaskFragment due in lock task mode.");
sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
- new IllegalStateException(
+ taskFragment, type, new IllegalStateException(
"Not allow to delete task fragment in lock task mode."));
break;
}
@@ -778,13 +779,15 @@
if (tf == null) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to operate with invalid fragment token");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf, type,
+ exception);
break;
}
if (tf.isEmbeddedTaskFragmentInPip()) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to start activity in PIP TaskFragment");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf, type,
+ exception);
break;
}
final Intent activityIntent = hop.getActivityIntent();
@@ -794,7 +797,7 @@
hop.getCallingActivity(), caller.mUid, caller.mPid,
errorCallbackToken);
if (!isStartResultSuccessful(result)) {
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf, type,
convertStartFailureToThrowable(result, activityIntent));
} else {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
@@ -815,28 +818,34 @@
if (parent == null || activity == null) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to operate with invalid fragment token or activity.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
if (parent.isEmbeddedTaskFragmentInPip()) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to reparent activity to PIP TaskFragment");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) {
final Throwable exception = new SecurityException(
"The task fragment is not allowed to embed the given activity.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
if (parent.getTask() != activity.getTask()) {
final Throwable exception = new SecurityException("The reparented activity is"
+ " not in the same Task as the target TaskFragment.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
+ prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(parent,
+ activity);
activity.reparent(parent, POSITION_TOP);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
@@ -851,14 +860,16 @@
if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to set adjacent on invalid fragment tokens");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf1, type,
+ exception);
break;
}
if (tf1.isEmbeddedTaskFragmentInPip()
|| (tf2 != null && tf2.isEmbeddedTaskFragmentInPip())) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to set adjacent on TaskFragment in PIP Task");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf1, type,
+ exception);
break;
}
tf1.setAdjacentTaskFragment(tf2);
@@ -1061,6 +1072,41 @@
return effects;
}
+ private void prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(
+ @NonNull TaskFragment taskFragment, @NonNull ActivityRecord activity) {
+ if (!taskFragment.canHaveEmbeddingActivityTransition(activity)) {
+ return;
+ }
+
+ // The reparent can happen in the following cases:
+ // 1. Reparent an existing activity to split when app launches new intent.
+ // - This happens after app calls to start activity, but before the activity is actually
+ // started, so we don't expect any collecting transition, but if it does, we can't
+ // queue the WCT because the start activity won't wait.
+ // 2. Reparent an existing activity to split to launch placeholder when Task size changed.
+ // - We expect to have a collecting transition for the Task resize, so just collect.
+ // 3. Reparent a new launching activity to an always-expand container.
+ // 4. Reparent a new launching activity to split to launch placeholder together.
+ // 5. Reparent a new launching activity to an existing split.
+ // - The new launching activity should have start an OPEN transition, so just collect.
+ // 6. Reparent PiP activity back to the original Task.
+ // - This should be part of the exiting PiP transition, so just collect.
+
+ if (!taskFragment.getBounds().equals(activity.getBounds()) && activity.isVisible()
+ && !mTransitionController.isCollecting()) {
+ // 1. Reparent an existing activity to split when app launches new intent.
+ mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, activity);
+ }
+
+ // We expect the activity to be in the transition already, so just collect the TaskFragment.
+ if (mTransitionController.isCollecting(activity)) {
+ taskFragment.collectEmbeddedTaskFragmentIfNeeded();
+ } else {
+ ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Reparenting Activity"
+ + " to embedded TaskFragment, but the Activity is not collected");
+ }
+ }
+
/** A helper method to send minimum dimension violation error to the client. */
private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
IBinder errorCallbackToken, String reason) {
@@ -1071,7 +1117,7 @@
+ taskFragment.getBounds() + " does not satisfy minimum dimensions:"
+ minDimensions + " " + reason);
sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(),
- errorCallbackToken, exception);
+ errorCallbackToken, taskFragment, -1 /* opType */, exception);
}
/**
@@ -1624,13 +1670,15 @@
if (ownerActivity == null || ownerActivity.getTask() == null) {
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid ownerToken");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
if (!ownerActivity.isResizeable()) {
final IllegalArgumentException exception = new IllegalArgumentException("Not allowed"
+ " to operate with non-resizable owner Activity");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
// The ownerActivity has to belong to the same app as the target Task.
@@ -1640,13 +1688,15 @@
final Throwable exception =
new SecurityException("Not allowed to operate with the ownerToken while "
+ "the root activity of the target task belong to the different app");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
if (ownerTask.inPinnedWindowingMode()) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to create TaskFragment in PIP Task");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
final TaskFragment taskFragment = new TaskFragment(mService,
@@ -1674,7 +1724,8 @@
if (newParentTF == null) {
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid container");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
if (newParentTF.getTaskFragmentOrganizer() != null) {
@@ -1685,20 +1736,23 @@
if (isEmbeddingDisallowed) {
final Throwable exception = new SecurityException(
"The new parent is not allowed to embed the activities.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
}
if (newParentTF.isEmbeddedTaskFragmentInPip() || oldParent.isEmbeddedTaskFragmentInPip()) {
final Throwable exception = new SecurityException(
"Not allow to reparent in TaskFragment in PIP Task.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
if (newParentTF.getTask() != oldParent.getTask()) {
final Throwable exception = new SecurityException(
"The new parent is not in the same Task as the old parent.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
while (oldParent.hasChild()) {
@@ -1713,7 +1767,8 @@
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid "
+ "taskFragment");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT, exception);
return 0;
}
if (taskFragment.isEmbeddedTaskFragmentInPip()
@@ -1722,7 +1777,8 @@
&& taskFragment.getTopNonFinishingActivity() != null) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to delete TaskFragment in PIP Task");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT, exception);
return 0;
}
mLaunchTaskFragments.removeAt(index);
@@ -1750,12 +1806,14 @@
}
void sendTaskFragmentOperationFailure(@NonNull ITaskFragmentOrganizer organizer,
- @Nullable IBinder errorCallbackToken, @NonNull Throwable exception) {
+ @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment, int opType,
+ @NonNull Throwable exception) {
if (organizer == null) {
throw new IllegalArgumentException("Not allowed to operate with invalid organizer");
}
mService.mTaskFragmentOrganizerController
- .onTaskFragmentError(organizer, errorCallbackToken, exception);
+ .onTaskFragmentError(organizer, errorCallbackToken, taskFragment, opType,
+ exception);
}
private Throwable convertStartFailureToThrowable(int result, Intent intent) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index af8c4c8..5c9cb05 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -137,7 +137,6 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.WINDOW;
@@ -2449,8 +2448,8 @@
final DisplayContent dc = getDisplayContent();
if (isImeLayeringTarget()) {
- // Remove the IME screenshot surface if the layering target is not animating.
- dc.removeImeScreenshotIfPossible();
+ // Remove the attached IME screenshot surface.
+ dc.removeImeSurfaceByTarget(this);
// Make sure to set mImeLayeringTarget as null when the removed window is the
// IME target, in case computeImeTarget may use the outdated target.
dc.setImeLayeringTarget(null);
@@ -3584,6 +3583,7 @@
} else {
logExclusionRestrictions(EXCLUSION_LEFT);
logExclusionRestrictions(EXCLUSION_RIGHT);
+ getDisplayContent().removeImeSurfaceByTarget(this);
}
// Exclude toast because legacy apps may show toast window by themselves, so the misused
// apps won't always be considered as foreground state.
@@ -4970,10 +4970,6 @@
|| isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}
- boolean isExitAnimationRunningSelfOrChild() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION);
- }
-
private boolean shouldFinishAnimatingExit() {
// Exit animation might be applied soon.
if (inTransition()) {
@@ -5927,6 +5923,10 @@
if (!super.prepareSync()) {
return false;
}
+ if (mIsWallpaper) {
+ // TODO(b/233286785): Add sync support to wallpaper.
+ return false;
+ }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
new file mode 100644
index 0000000..7acb6d6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.audio;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.VolumeInfo;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AudioDeviceVolumeManagerTest {
+ private static final String TAG = "AudioDeviceVolumeManagerTest";
+
+ private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+
+ private Context mContext;
+ private String mPackageName;
+ private AudioSystemAdapter mSpyAudioSystem;
+ private SystemServerAdapter mSystemServer;
+ private SettingsAdapter mSettingsAdapter;
+ private TestLooper mTestLooper;
+
+ private AudioService mAudioService;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
+ mContext = InstrumentationRegistry.getTargetContext();
+ mPackageName = mContext.getOpPackageName();
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+
+ mSystemServer = new NoOpSystemServerAdapter();
+ mSettingsAdapter = new NoOpSettingsAdapter();
+ mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
+ mSettingsAdapter, mTestLooper.getLooper()) {
+ @Override
+ public int getDeviceForStream(int stream) {
+ return AudioSystem.DEVICE_OUT_SPEAKER;
+ }
+ };
+
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void testSetDeviceVolume() {
+ AudioManager am = mContext.getSystemService(AudioManager.class);
+ final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final int midIndex = (minIndex + maxIndex) / 2;
+ final VolumeInfo volMedia = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMinVolumeIndex(minIndex)
+ .setMaxVolumeIndex(maxIndex)
+ .build();
+ final VolumeInfo volMin = new VolumeInfo.Builder(volMedia).setVolumeIndex(minIndex).build();
+ final VolumeInfo volMid = new VolumeInfo.Builder(volMedia).setVolumeIndex(midIndex).build();
+ final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes(
+ /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla");
+
+ mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG);
+ mTestLooper.dispatchAll();
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+
+ mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG);
+ mTestLooper.dispatchAll();
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 09e5d4b..ee9d59b 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -126,6 +126,11 @@
}
@Override
+ public int setStreamVolumeIndexAS(int stream, int index, int device) {
+ return AudioSystem.AUDIO_STATUS_OK;
+ }
+
+ @Override
@NonNull
public ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
@NonNull AudioAttributes attributes, boolean forVolume) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 1a49f8a..ea1e49d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -43,8 +43,10 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -128,6 +130,8 @@
@Captor
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+ private TestLooper mLooper = new TestLooper();
+
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -233,6 +237,9 @@
client.start(mCallback);
client.onAuthenticated(new Fingerprint("name", 2 /* enrollmentId */, SENSOR_ID),
true /* authenticated */, new ArrayList<>());
+
+ mLooper.moveTimeForward(10);
+ mLooper.dispatchAll();
verify(mLuxProbe).destroy();
client.onAcquired(2, 0);
@@ -309,9 +316,58 @@
client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
true /* authenticated */, new ArrayList<>());
+ mLooper.moveTimeForward(10);
+ mLooper.dispatchAll();
verify(mCancellationSignal).cancel();
}
+ @Test
+ public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPowerPressed();
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ client.onPowerPressed();
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void fingerprintAuthSucceedsAfterPowerWindow() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPowerPressed();
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
@@ -336,7 +392,8 @@
9 /* sensorId */, mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, mLockoutCache,
- mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps) {
+ mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps,
+ new Handler(mLooper.getLooper())) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 371861f..7746bd6 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -53,33 +53,30 @@
/**
* Unit tests for {@link SideFpsEventHandler}.
- * <p/>
- * Run with <code>atest SideFpsEventHandlerTest</code>.
+ *
+ * <p>Run with <code>atest SideFpsEventHandlerTest</code>.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class SideFpsEventHandlerTest {
- private static final List<Integer> sAllStates = List.of(
- BiometricStateListener.STATE_IDLE,
- BiometricStateListener.STATE_ENROLLING,
- BiometricStateListener.STATE_KEYGUARD_AUTH,
- BiometricStateListener.STATE_BP_AUTH,
- BiometricStateListener.STATE_AUTH_OTHER);
+ private static final List<Integer> sAllStates =
+ List.of(
+ BiometricStateListener.STATE_IDLE,
+ BiometricStateListener.STATE_ENROLLING,
+ BiometricStateListener.STATE_KEYGUARD_AUTH,
+ BiometricStateListener.STATE_BP_AUTH,
+ BiometricStateListener.STATE_AUTH_OTHER);
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private FingerprintManager mFingerprintManager;
- @Spy
- private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
- @Mock
- private AlertDialog mAlertDialog;
- @Mock
- private Window mWindow;
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private FingerprintManager mFingerprintManager;
+ @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
+ @Mock private AlertDialog mAlertDialog;
+ @Mock private Window mWindow;
private TestLooper mLooper = new TestLooper();
private SideFpsEventHandler mEventHandler;
@@ -95,9 +92,12 @@
when(mDialogBuilder.create()).thenReturn(mAlertDialog);
when(mAlertDialog.getWindow()).thenReturn(mWindow);
- mEventHandler = new SideFpsEventHandler(
- mContext, new Handler(mLooper.getLooper()),
- mContext.getSystemService(PowerManager.class), () -> mDialogBuilder);
+ mEventHandler =
+ new SideFpsEventHandler(
+ mContext,
+ new Handler(mLooper.getLooper()),
+ mContext.getSystemService(PowerManager.class),
+ () -> mDialogBuilder);
}
@Test
@@ -105,7 +105,7 @@
when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
.thenReturn(false);
- assertThat(mEventHandler.onSinglePressDetected(60L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -117,7 +117,7 @@
for (int state : sAllStates) {
setBiometricState(state);
- assertThat(mEventHandler.onSinglePressDetected(200L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -130,7 +130,7 @@
for (int state : sAllStates) {
setBiometricState(state);
- assertThat(mEventHandler.onSinglePressDetected(400L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -139,13 +139,13 @@
@Test
public void ignoresWhenIdleOrUnknown() throws Exception {
- setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setupWithSensor(true /* hasSidefps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_IDLE);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
setBiometricState(BiometricStateListener.STATE_AUTH_OTHER);
- assertThat(mEventHandler.onSinglePressDetected(90000L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -156,7 +156,7 @@
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_KEYGUARD_AUTH);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -164,13 +164,13 @@
@Test
public void promptsWhenBPisActive() throws Exception {
- setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setupWithSensor(true /* hasSideFps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_BP_AUTH);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
+ verify(mAlertDialog, never()).show();
}
@Test
@@ -178,7 +178,57 @@
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+ verify(mAlertDialog, never()).dismiss();
+ }
+
+ @Test
+ public void dismissesDialogOnTouchWhenEnrolling() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mAlertDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ mLooper.moveTimeForward(10000);
+ mLooper.dispatchAll();
+
+ verify(mAlertDialog).dismiss();
+ }
+
+ @Test
+ public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mAlertDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).dismiss();
+ }
+
+ @Test
+ public void showDialogAfterTap() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mAlertDialog.isShowing()).thenReturn(true);
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
@@ -201,11 +251,13 @@
ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture());
if (initialized) {
- fpCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
- List.of(mock(FingerprintSensorPropertiesInternal.class)));
+ fpCallbackCaptor
+ .getValue()
+ .onAllAuthenticatorsRegistered(
+ List.of(mock(FingerprintSensorPropertiesInternal.class)));
if (hasSfps) {
- ArgumentCaptor<BiometricStateListener> captor = ArgumentCaptor.forClass(
- BiometricStateListener.class);
+ ArgumentCaptor<BiometricStateListener> captor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
verify(mFingerprintManager).registerBiometricStateListener(captor.capture());
mBiometricStateListener = captor.getValue();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index bd7186e..2f92260 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -26,7 +25,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.content.Context;
@@ -60,8 +58,6 @@
Handler mFileWriteHandler;
@Mock
Context mContext;
- @Mock
- AlarmManager mAlarmManager;
NotificationHistoryDatabase mDataBase;
@@ -96,22 +92,16 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
when(mContext.getUser()).thenReturn(getContext().getUser());
when(mContext.getPackageName()).thenReturn(getContext().getPackageName());
mRootDir = new File(mContext.getFilesDir(), "NotificationHistoryDatabaseTest");
- mDataBase = new NotificationHistoryDatabase(mContext, mFileWriteHandler, mRootDir);
+ mDataBase = new NotificationHistoryDatabase(mFileWriteHandler, mRootDir);
mDataBase.init();
}
@Test
- public void testDeletionReceiver() {
- verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
- }
-
- @Test
public void testPrune() throws Exception {
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis(10);
@@ -144,8 +134,6 @@
mDataBase.prune(retainDays, cal.getTimeInMillis());
assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
-
- verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
}
@Test
@@ -412,15 +400,14 @@
when(file.getName()).thenReturn("5");
when(af.getBaseFile()).thenReturn(file);
- wbr.run(5, af);
+ wbr.run(af);
assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(1);
assertThat(mDataBase.mBuffer).isNotEqualTo(nh);
- verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
}
@Test
- public void testRemoveFilePathFromHistory_hasMatch() throws Exception {
+ public void testRemoveFilePathFromHistory_hasMatch() {
for (int i = 0; i < 5; i++) {
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
@@ -436,7 +423,7 @@
}
@Test
- public void testRemoveFilePathFromHistory_noMatch() throws Exception {
+ public void testRemoveFilePathFromHistory_noMatch() {
for (int i = 0; i < 5; i++) {
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
new file mode 100644
index 0000000..af10b9d
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.app.job.JobServiceEngine;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidTestingRunner.class)
+public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
+ private NotificationHistoryJobService mJobService;
+ private JobParameters mJobParams = new JobParameters(null,
+ NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
+ 0, false, false, null, null, null);
+
+ @Captor
+ ArgumentCaptor<JobInfo> mJobInfoCaptor;
+
+ @Mock
+ private JobScheduler mMockJobScheduler;
+
+ @Mock
+ private NotificationManagerInternal mMockNotificationManagerInternal;
+
+ @Rule
+ public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mJobService = new NotificationHistoryJobService();
+
+ final Field field = JobService.class.getDeclaredField("mEngine");
+ field.setAccessible(true);
+ field.set(mJobService, mock(JobServiceEngine.class));
+ mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
+
+ // add NotificationManagerInternal to LocalServices
+ LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.addService(NotificationManagerInternal.class,
+ mMockNotificationManagerInternal);
+ }
+
+ @Test
+ public void testScheduleJob() {
+ // if asked, the job doesn't currently exist yet
+ when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
+
+ // attempt to schedule the job
+ NotificationHistoryJobService.scheduleJob(mContext);
+ verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture());
+
+ // verify various properties of the job that is passed in to the job scheduler
+ JobInfo jobInfo = mJobInfoCaptor.getValue();
+ assertEquals(NotificationHistoryJobService.BASE_JOB_ID, jobInfo.getId());
+ assertFalse(jobInfo.isPersisted());
+ assertTrue(jobInfo.isPeriodic());
+ }
+
+ @Test
+ public void testOnStartJob() {
+ assertTrue(mJobService.onStartJob(mJobParams));
+
+ verify(mMockNotificationManagerInternal, timeout(500).atLeastOnce()).cleanupHistoryFiles();
+ }
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index b1b323b..443ed58 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -8369,7 +8369,7 @@
public void testOnUnlockUser() {
UserInfo ui = new UserInfo();
ui.id = 10;
- mService.onUserUnlocking(new TargetUser(ui));
+ mService.onUserUnlocked(new TargetUser(ui));
waitForIdle();
verify(mHistoryManager, timeout(MAX_POST_DELAY).times(1)).onUserUnlocked(ui.id);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3f3d01a..0c3b270 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -49,6 +49,7 @@
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -2538,6 +2539,21 @@
}
@Test
+ public void testStuckExitingWindow() {
+ final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
+ "closingWindow");
+ closingWindow.mAnimatingExit = true;
+ closingWindow.mRemoveOnExit = true;
+ closingWindow.mActivityRecord.commitVisibility(
+ false /* visible */, true /* performLayout */);
+
+ // We pretended that we were running an exit animation, but that should have been cleared up
+ // by changing visibility of ActivityRecord
+ closingWindow.removeIfPossible();
+ assertTrue(closingWindow.mRemoved);
+ }
+
+ @Test
public void testSetOrientation() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setVisible(true);
@@ -3133,7 +3149,6 @@
mDisplayContent.mOpeningApps.clear();
app.mActivityRecord.commitVisibility(false, false);
app.mActivityRecord.onWindowsGone();
- mDisplayContent.computeImeTargetIfNeeded(app.mActivityRecord);
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 1f07b20..d6b807f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2207,6 +2207,52 @@
assertNotEquals(curSnapshot, mDisplayContent.mImeScreenshot);
}
+ @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+ @Test
+ public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ win.onSurfaceShownChanged(true);
+ makeWindowVisible(win, mDisplayContent.mInputMethodWindow);
+ task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
+ doReturn(true).when(task).okToAnimate();
+ ArrayList<WindowContainer> sources = new ArrayList<>();
+ sources.add(activity);
+
+ mDisplayContent.setImeLayeringTarget(win);
+ mDisplayContent.setImeInputTarget(win);
+ mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
+ task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
+ false /* isVoiceInteraction */, sources);
+ assertNotNull(mDisplayContent.mImeScreenshot);
+
+ win.onSurfaceShownChanged(false);
+ assertNull(mDisplayContent.mImeScreenshot);
+ }
+
+ @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+ @Test
+ public void testRemoveImeScreenshot_whenWindowRemoveImmediately() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ makeWindowVisible(mDisplayContent.mInputMethodWindow);
+
+ mDisplayContent.setImeLayeringTarget(win);
+ mDisplayContent.setImeInputTarget(win);
+ mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
+ mDisplayContent.showImeScreenshot();
+ assertNotNull(mDisplayContent.mImeScreenshot);
+
+ // Expect IME snapshot will be removed when the win is IME layering target and invoked
+ // removeImeSurfaceByTarget.
+ win.removeImmediately();
+ assertNull(mDisplayContent.mImeScreenshot);
+ }
+
@Test
public void testRotateBounds_keepSamePhysicalPosition() {
final DisplayContent dc =
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 e47bcc9..97f0918 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -18,6 +18,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -237,10 +243,10 @@
mController.registerOrganizer(mIOrganizer);
mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
- mErrorToken, exception);
+ mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(exception));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception));
}
@Test
@@ -604,7 +610,9 @@
verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(),
any(), any(), anyInt(), anyInt(), any());
verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment),
+ eq(HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
}
@Test
@@ -619,7 +627,9 @@
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment),
+ eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
assertNull(activity.getOrganizedTaskFragment());
}
@@ -635,7 +645,9 @@
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment),
+ eq(HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS),
+ any(IllegalArgumentException.class));
verify(mTaskFragment, never()).setAdjacentTaskFragment(any());
}
@@ -654,7 +666,8 @@
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(null), eq(HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
}
@@ -669,7 +682,8 @@
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment), eq(HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
assertNotNull(mWindowOrganizerController.getTaskFragment(mFragmentToken));
// Allow organizer to delete empty TaskFragment for cleanup.
@@ -931,7 +945,9 @@
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(SecurityException.class));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
+ any(SecurityException.class));
}
@Test
@@ -968,7 +984,8 @@
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(SecurityException.class));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5f30963..1096351 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -23,6 +23,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Process.FIRST_APPLICATION_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -31,6 +32,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -433,6 +437,40 @@
}
@Test
+ public void testIsAllowedToEmbedActivity() {
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .createActivityCount(1)
+ .build();
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+ // Not allow embedding activity if not a trusted host.
+ doReturn(false).when(taskFragment).isAllowedToEmbedActivityInUntrustedMode(any());
+ doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
+ assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+ taskFragment.isAllowedToEmbedActivity(activity));
+
+ // Not allow embedding activity if the TaskFragment is smaller than activity min dimension.
+ doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
+ doReturn(true).when(taskFragment).smallerThanMinDimension(any());
+ assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+ taskFragment.isAllowedToEmbedActivity(activity));
+
+ // Not allow to start activity across TaskFragments for result.
+ final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(taskFragment.getTask())
+ .build();
+ final ActivityRecord newActivity = new ActivityBuilder(mAtm)
+ .setUid(FIRST_APPLICATION_UID)
+ .build();
+ doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
+ doReturn(false).when(newTaskFragment).smallerThanMinDimension(any());
+ newActivity.resultTo = activity;
+ assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
+ newTaskFragment.isAllowedToEmbedActivity(newActivity));
+ }
+
+ @Test
public void testIgnoreRequestedOrientationForActivityEmbeddingSplit() {
// Setup two activities in ActivityEmbedding split.
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 446ec8b..cfc0da7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1003,7 +1003,6 @@
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
- mDisplayContent.computeImeTargetIfNeeded(app.mActivityRecord);
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}