Merge "Define stub and data classes for SharedConnectivity Service"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 5998ab7..051dde0 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -286,7 +286,7 @@
* this test can be removed.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startUser_startOnceBefore() throws RemoteException {
+ public void startUser_startedOnceBefore() throws RemoteException {
startUser_measuresAfterFirstIterations(/* numberOfIterationsToSkip */1);
}
@@ -340,7 +340,7 @@
* The next iterations take the expected time to start a user.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startUser_startTwiceBefore() throws RemoteException {
+ public void startUser_startedTwiceBefore() throws RemoteException {
final int userId = createUserNoFlags();
//TODO(b/266681181) Reduce iteration number by 1 after investigation and possible fix.
@@ -411,7 +411,7 @@
* The next iterations take the expected time to start a user.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startAndUnlockUser_startTwiceBefore() throws RemoteException {
+ public void startAndUnlockUser_startedTwiceBefore() throws RemoteException {
final int userId = createUserNoFlags();
//TODO(b/266681181) Reduce iteration number by 1 after investigation and possible fix.
diff --git a/core/api/current.txt b/core/api/current.txt
index d3de158..9851428 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24633,7 +24633,8 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
- field public static final int FLAG_SUGGESTED_ROUTE = 2; // 0x2
+ field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2
+ field public static final int FLAG_SUGGESTED_ROUTE = 4; // 0x4
field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
@@ -39329,6 +39330,19 @@
}
+package android.service.assist.classification {
+
+ public final class FieldClassification implements android.os.Parcelable {
+ ctor public FieldClassification(@NonNull android.view.autofill.AutofillId, @NonNull java.util.Set<java.lang.String>);
+ method public int describeContents();
+ method @NonNull public android.view.autofill.AutofillId getAutofillId();
+ method @NonNull public java.util.Set<java.lang.String> getHints();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.assist.classification.FieldClassification> CREATOR;
+ }
+
+}
+
package android.service.autofill {
public abstract class AutofillService extends android.app.Service {
@@ -40298,7 +40312,7 @@
ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.credentials.CredentialOption);
method public int describeContents();
method @NonNull public android.service.credentials.CallingAppInfo getCallingAppInfo();
- method @NonNull public android.credentials.CredentialOption getGetCredentialOption();
+ method @NonNull public android.credentials.CredentialOption getCredentialOption();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialRequest> CREATOR;
}
@@ -42457,7 +42471,7 @@
method @NonNull public String getDisplayName();
method @NonNull public android.os.Bundle getExtras();
method public int getState();
- method public void setStreamingState(int);
+ method public void requestStreamingState(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
field public static final int STATE_DISCONNECTED = 3; // 0x3
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2ad9a9b..4a0b2eb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -55,6 +55,7 @@
field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
+ field public static final String BIND_FIELD_CLASSIFICATION_SERVICE = "android.permission.BIND_FIELD_CLASSIFICATION_SERVICE";
field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
field public static final String BIND_HOTWORD_DETECTION_SERVICE = "android.permission.BIND_HOTWORD_DETECTION_SERVICE";
field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
@@ -2121,7 +2122,7 @@
method @Nullable public void query(@NonNull android.app.search.Query, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
method public void registerEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
method public void requestEmptyQueryResultUpdate();
- method public void unregisterEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
+ method public void unregisterEmptyQueryResultUpdateCallback(@NonNull android.app.search.SearchSession.Callback);
}
public static interface SearchSession.Callback {
@@ -11675,6 +11676,39 @@
}
+package android.service.assist.classification {
+
+ public final class FieldClassification implements android.os.Parcelable {
+ ctor public FieldClassification(@NonNull android.view.autofill.AutofillId, @NonNull java.util.Set<java.lang.String>, @NonNull java.util.Set<java.lang.String>);
+ method @NonNull public java.util.Set<java.lang.String> getGroupHints();
+ }
+
+ public final class FieldClassificationRequest implements android.os.Parcelable {
+ ctor public FieldClassificationRequest(@NonNull android.app.assist.AssistStructure);
+ method public int describeContents();
+ method @NonNull public android.app.assist.AssistStructure getAssistStructure();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.assist.classification.FieldClassificationRequest> CREATOR;
+ }
+
+ public final class FieldClassificationResponse implements android.os.Parcelable {
+ ctor public FieldClassificationResponse(@NonNull java.util.Set<android.service.assist.classification.FieldClassification>);
+ method public int describeContents();
+ method @NonNull public java.util.Set<android.service.assist.classification.FieldClassification> getClassifications();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.assist.classification.FieldClassificationResponse> CREATOR;
+ }
+
+ public abstract class FieldClassificationService extends android.app.Service {
+ ctor public FieldClassificationService();
+ method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
+ method public void onConnected();
+ method public void onDisconnected();
+ field public static final String SERVICE_INTERFACE = "android.service.assist.classification.FieldClassificationService";
+ }
+
+}
+
package android.service.attention {
public abstract class AttentionService extends android.app.Service {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b4b5323..b3e3f35 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -144,8 +144,6 @@
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL
- field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf
- field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
@@ -822,7 +820,6 @@
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
- field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -3168,9 +3165,7 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
- method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
- method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c4d6ad7..183357a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -630,6 +630,8 @@
PROCESS_CAPABILITY_FOREGROUND_LOCATION,
PROCESS_CAPABILITY_FOREGROUND_CAMERA,
PROCESS_CAPABILITY_FOREGROUND_MICROPHONE,
+ PROCESS_CAPABILITY_NETWORK,
+ PROCESS_CAPABILITY_BFSL,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProcessCapability {}
@@ -654,20 +656,36 @@
@TestApi
public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3;
- /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/
- @TestApi
+ /**
+ * Flag used to indicate whether an app is allowed to start a foreground service from the
+ * background, decided by the procstates. ("BFSL" == "background foreground service launch")
+ *
+ * - BFSL has a number of exemptions -- e.g. when an app is power-allowlisted, including
+ * temp-allowlist -- but this capability is *not* used to represent such exemptions.
+ * This is set only based on the procstate and the foreground service type.
+ * - Basically, procstates <= BFGS (i.e. BFGS, FGS, BTOP, TOP, ...) are BFSL-allowed,
+ * and that's how things worked on Android S/T.
+ * However, Android U added a "SHORT_SERVICE" FGS type, which gets the FGS procstate
+ * *but* can't start another FGS. So now we use this flag to decide whether FGS/BFGS
+ * procstates are BFSL-allowed. (higher procstates, such as BTOP, will still always be
+ * BFSL-allowed.)
+ * We propagate this flag across via service bindings and provider references.
+ *
+ * @hide
+ */
+ public static final int PROCESS_CAPABILITY_BFSL = 1 << 4;
+
+ /**
+ * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
+ *
+ * Don't expose it as TestApi -- we may add new capabilities any time, which could
+ * break CTS tests if they relied on it.
+ */
public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
| PROCESS_CAPABILITY_FOREGROUND_CAMERA
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
- | PROCESS_CAPABILITY_NETWORK;
- /**
- * All explicit capabilities. These are capabilities that need to be specified from manifest
- * file.
- * @hide
- */
- @TestApi
- public static final int PROCESS_CAPABILITY_ALL_EXPLICIT =
- PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+ | PROCESS_CAPABILITY_NETWORK
+ | PROCESS_CAPABILITY_BFSL;
/**
* All implicit capabilities. There are capabilities that process automatically have.
@@ -686,6 +704,7 @@
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
pw.print((caps & PROCESS_CAPABILITY_NETWORK) != 0 ? 'N' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
}
/** @hide */
@@ -694,6 +713,7 @@
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
sb.append((caps & PROCESS_CAPABILITY_NETWORK) != 0 ? 'N' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
}
/**
@@ -702,13 +722,10 @@
*/
public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
printCapabilitiesSummary(pw, caps);
- final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION
- | PROCESS_CAPABILITY_FOREGROUND_CAMERA
- | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
- | PROCESS_CAPABILITY_NETWORK);
+ final int remain = caps & ~PROCESS_CAPABILITY_ALL;
if (remain != 0) {
- pw.print('+');
- pw.print(remain);
+ pw.print("+0x");
+ pw.print(Integer.toHexString(remain));
}
}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index d8eb03e..20d19c1 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -109,7 +109,7 @@
*/
// TODO (b/254661666): Change to @EnabledAfter(T)
@ChangeId
- @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @Disabled
@Overridable
public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
@@ -144,7 +144,7 @@
*/
// TODO (b/254661666): Change to @EnabledAfter(T)
@ChangeId
- @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @Disabled
@Overridable
public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
@@ -1059,7 +1059,7 @@
if (policy.isTypeDisabled(callerUid)) {
return FGS_TYPE_POLICY_CHECK_DISABLED;
}
- int permissionResult = PERMISSION_GRANTED;
+ int permissionResult = PERMISSION_DENIED;
// Do we have the permission to start FGS with this type.
if (policy.mAllOfPermissions != null) {
permissionResult = policy.mAllOfPermissions.checkPermissions(context,
diff --git a/core/java/android/app/RemoteLockscreenValidationResult.aidl b/core/java/android/app/RemoteLockscreenValidationResult.aidl
new file mode 100644
index 0000000..504f78f
--- /dev/null
+++ b/core/java/android/app/RemoteLockscreenValidationResult.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** {@hide} */
+parcelable RemoteLockscreenValidationResult;
diff --git a/core/java/android/app/RemoteLockscreenValidationResult.java b/core/java/android/app/RemoteLockscreenValidationResult.java
new file mode 100644
index 0000000..4f15be2
--- /dev/null
+++ b/core/java/android/app/RemoteLockscreenValidationResult.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/**
+ * Result of lock screen credentials verification.
+ *
+ * @hide
+ */
+public final class RemoteLockscreenValidationResult implements Parcelable {
+
+ /**
+ * The guess was correct
+ */
+ public static final int RESULT_GUESS_VALID = 1;
+
+ /**
+ * Remote device provided incorrect credentials.
+ */
+ public static final int RESULT_GUESS_INVALID = 2;
+
+ /**
+ * The operation was canceled because the API is locked out due to too many attempts. It
+ * usually happens after 5 failed attempts and API may be called again after a short
+ * delay specified by {@code getTimeoutMillis}.
+ */
+ public static final int RESULT_LOCKOUT = 3;
+
+ /**
+ * There were too many invalid guesses.
+ */
+ public static final int RESULT_NO_REMAINING_ATTEMPTS = 4;
+
+ @IntDef({RESULT_GUESS_VALID,
+ RESULT_GUESS_INVALID,
+ RESULT_LOCKOUT,
+ RESULT_NO_REMAINING_ATTEMPTS})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ResultCode {}
+
+ private int mResultCode;
+ private long mTimeoutMillis;
+
+ public static final @NonNull Parcelable.Creator<RemoteLockscreenValidationResult> CREATOR =
+ new Parcelable.Creator<RemoteLockscreenValidationResult>() {
+ @Override
+ public RemoteLockscreenValidationResult createFromParcel(Parcel source) {
+ return new RemoteLockscreenValidationResult(source);
+ }
+
+ @Override
+ public RemoteLockscreenValidationResult[] newArray(int size) {
+ return new RemoteLockscreenValidationResult[size];
+ }
+ };
+
+ /**
+ * Builder for {@code RemoteLockscreenValidationResult}
+ */
+ public static final class Builder {
+ private RemoteLockscreenValidationResult mInstance = new RemoteLockscreenValidationResult();
+
+ /**
+ * Sets the result code.
+ */
+ public @NonNull Builder setResultCode(@ResultCode int resultCode) {
+ mInstance.mResultCode = resultCode;
+ return this;
+ }
+
+ /**
+ * Sets timeout for {@code RESULT_LOCKOUT}.
+ * Default value is {@code 0}.
+ */
+ public @NonNull Builder setTimeoutMillis(@DurationMillisLong long timeoutMillis) {
+ mInstance.mTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ /**
+ * Creates {@code RemoteLockscreenValidationResult}.
+ *
+ * @throws IllegalStateException if result code was not set.
+ */
+ public @NonNull RemoteLockscreenValidationResult build() {
+ if (mInstance.mResultCode == 0) {
+ throw new IllegalStateException("Result code must be set");
+ }
+ return mInstance;
+ }
+ }
+
+ /**
+ * Gets the result code.
+ */
+ public @ResultCode int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Delay before next attempt to verify credentials.
+ *
+ * Default value is {@code 0}.
+ */
+ public @DurationMillisLong long getTimeoutMillis() {
+ return mTimeoutMillis;
+ }
+
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mResultCode);
+ out.writeLong(mTimeoutMillis);
+ }
+
+ private RemoteLockscreenValidationResult() {
+ }
+
+ private RemoteLockscreenValidationResult(Parcel in) {
+ mResultCode = in.readInt();
+ mTimeoutMillis = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/app/StartLockscreenValidationRequest.aidl b/core/java/android/app/StartLockscreenValidationRequest.aidl
new file mode 100644
index 0000000..367dfee
--- /dev/null
+++ b/core/java/android/app/StartLockscreenValidationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** {@hide} */
+parcelable StartLockscreenValidationRequest;
diff --git a/core/java/android/app/StartLockscreenValidationRequest.java b/core/java/android/app/StartLockscreenValidationRequest.java
new file mode 100644
index 0000000..69c268bcb
--- /dev/null
+++ b/core/java/android/app/StartLockscreenValidationRequest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.app.KeyguardManager.LockTypes;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Provides information necessary to perform remote lock screen credentials check.
+ *
+ * @hide
+ */
+public final class StartLockscreenValidationRequest implements Parcelable {
+
+ @LockTypes
+ private int mLockscreenUiType;
+
+ private byte[] mSourcePublicKey;
+
+ private int mRemainingAttempts;
+
+ public static final @NonNull Parcelable.Creator<StartLockscreenValidationRequest> CREATOR = new
+ Parcelable.Creator<StartLockscreenValidationRequest>() {
+ @Override
+ public StartLockscreenValidationRequest createFromParcel(Parcel source) {
+ return new StartLockscreenValidationRequest(source);
+ }
+
+ @Override
+ public StartLockscreenValidationRequest[] newArray(int size) {
+ return new StartLockscreenValidationRequest[size];
+ }
+ };
+
+
+ /**
+ * Builder for {@code StartLockscreenValidationRequest}
+ */
+ public static final class Builder {
+ private StartLockscreenValidationRequest mInstance = new StartLockscreenValidationRequest();
+
+ /**
+ * Sets UI type.
+ * Default value is {@code LockTypes.PASSWORD}
+ *
+ * @param lockscreenUiType The UI format
+ * @return This builder.
+ */
+ public @NonNull Builder setLockscreenUiType(@LockTypes int lockscreenUiType) {
+ mInstance.mLockscreenUiType = lockscreenUiType;
+ return this;
+ }
+
+ /**
+ * Sets public key using secure box encoding
+ * @return This builder.
+ */
+ public @NonNull Builder setSourcePublicKey(@NonNull byte[] publicKey) {
+ mInstance.mSourcePublicKey = publicKey;
+ return this;
+ }
+
+ /**
+ * Sets the number of remaining credentials check
+ * Default value is {@code 0}
+ *
+ * @return This builder.
+ */
+ public @NonNull Builder setRemainingAttempts(int remainingAttempts) {
+ mInstance.mRemainingAttempts = remainingAttempts;
+ return this;
+ }
+
+ /**
+ * Creates {@code StartLockscreenValidationRequest}
+ *
+ * @throws NullPointerException if required fields are not set.
+ */
+ public @NonNull StartLockscreenValidationRequest build() {
+ Objects.requireNonNull(mInstance.mSourcePublicKey);
+ return mInstance;
+ }
+ }
+
+ /**
+ * Specifies lock screen credential type.
+ */
+ public @LockTypes int getLockscreenUiType() {
+ return mLockscreenUiType;
+ }
+
+ /**
+ * Public key used to send encrypted credentials.
+ */
+ public @NonNull byte[] getSourcePublicKey() {
+ return mSourcePublicKey;
+ }
+
+ /**
+ * Number of remaining attempts to verify credentials.
+ *
+ * <p>After correct guess counter is reset to {@code 5}.
+ */
+ public int getRemainingAttempts() {
+ return mRemainingAttempts;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mLockscreenUiType);
+ out.writeByteArray(mSourcePublicKey);
+ out.writeInt(mRemainingAttempts);
+ }
+
+ private StartLockscreenValidationRequest() {
+ }
+
+ private StartLockscreenValidationRequest(Parcel in) {
+ mLockscreenUiType = in.readInt();
+ mSourcePublicKey = in.createByteArray();
+ mRemainingAttempts = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index e323e89..909073e 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -265,15 +265,9 @@
public void onCreate() {
}
- /**
- * Provided as a convenience for agent implementations that need an opportunity
- * to do one-time initialization before the actual backup or restore operation
- * is begun with information about the calling user.
- * <p>
- *
- * @hide
- */
+ /** @hide */
public void onCreate(UserHandle user) {
+ mUser = user;
onCreate();
}
@@ -284,7 +278,6 @@
*/
@Deprecated
public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
- mUser = user;
mBackupDestination = backupDestination;
onCreate(user);
@@ -295,7 +288,6 @@
*/
public void onCreate(UserHandle user, @BackupDestination int backupDestination,
@OperationType int operationType) {
- mUser = user;
mBackupDestination = backupDestination;
mLogger = new BackupRestoreEventLogger(operationType);
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index eda68dd..9e0a1d0 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -204,7 +204,6 @@
* @param callback The callback to be unregistered.
*/
public void unregisterEmptyQueryResultUpdateCallback(
- @NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Callback callback) {
synchronized (mRegisteredCallbacks) {
if (mIsClosed.get()) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 83f0894..db72e29 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1149,34 +1149,6 @@
264301586L; // buganizer id
/**
- * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
- * an activity bounds for:
- *
- * <p>{@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View}#getWindowDisplayFrame,
- * {@link android.view.View}#getBoundsOnScreen.
- *
- * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
- * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
- * through
- * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
- * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
- *
- * <p>Some applications assume that they occupy the whole screen and therefore use the display
- * coordinates in their calculations as if an activity is positioned in the top-left corner of
- * the screen, with left coordinate equal to 0. This may not be the case of applications in
- * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
- * case the activity is Letterboxed or is in multi-window mode.
- * @hide
- */
- @ChangeId
- @Overridable
- @Disabled
- @TestApi
- public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
-
- /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 653998f..b29efab 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10271,6 +10271,17 @@
"active_unlock_on_unlock_intent_when_biometric_enrolled";
/**
+ * If active unlock triggers on unlock intents, then also request active unlock on
+ * these wake-up reasons. See PowerManager.WakeReason for value mappings.
+ * WakeReasons should be separated by a pipe. For example: "0|3" or "0". If this
+ * setting should be disabled, then this should be set to an empty string. A null value
+ * will use the system default value (WAKE_REASON_UNFOLD_DEVICE).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS =
+ "active_unlock_wakeups_considered_unlock_intents";
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
@@ -11457,6 +11468,13 @@
"extra_automatic_power_save_mode";
/**
+ * Whether lockscreen weather is enabled.
+ *
+ * @hide
+ */
+ public static final String LOCK_SCREEN_WEATHER_ENABLED = "lockscreen_weather_enabled";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/service/assist/OWNERS b/core/java/android/service/assist/OWNERS
new file mode 100644
index 0000000..533b1f1
--- /dev/null
+++ b/core/java/android/service/assist/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 351486
+
+include /core/java/android/view/autofill/OWNERS
\ No newline at end of file
diff --git a/core/java/android/service/assist/classification/FieldClassification.aidl b/core/java/android/service/assist/classification/FieldClassification.aidl
new file mode 100644
index 0000000..7d0c078
--- /dev/null
+++ b/core/java/android/service/assist/classification/FieldClassification.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+parcelable FieldClassification;
diff --git a/core/java/android/service/assist/classification/FieldClassification.java b/core/java/android/service/assist/classification/FieldClassification.java
new file mode 100644
index 0000000..0ea8112
--- /dev/null
+++ b/core/java/android/service/assist/classification/FieldClassification.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.view.autofill.AutofillId;
+
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a classified field from the detection service.
+ */
+// TODO(b/266930067): Once @SystemApi is supported, use genSetters and genConstructor.
+@DataClass(
+ genToString = true,
+ genConstructor = false
+)
+public final class FieldClassification implements Parcelable {
+
+ /**
+ * Autofill id of the detected field
+ */
+ private final @NonNull AutofillId mAutofillId;
+
+ /**
+ * Detected fields types represented as autofill hints
+ *
+ * A particular field can be detected as multiple types. For eg: A sign-in field may take in a
+ * username, an email address or a phone number. In such cases, it should be detected as
+ * "username", "emailAddress" and "phoneNumber"
+ *
+ * The value of these hints are contained in androidx.autofill.HintConstants
+ */
+ private final @NonNull Set<String> mHints;
+
+
+ /**
+ * Group hints are the hints that may represent the group of related hints (including
+ * themselves). The value of these group hints are contained in androidx.autofill.HintConstants
+ *
+ * <p>
+ *
+ * "creditCardNumber" is the group hint for hints containing credit card related fields:
+ * "creditCardNumber", "creditCardExpirationDate", "creditCardExpirationDay",
+ * "creditCardExpirationMonth", "creditCardExpirationYear", "creditCardSecurityCode",
+ *
+ * <p>
+ *
+ * "postalAddress" is the group hint for hints all postal address related fields:
+ * "postalAddress", "streetAddress", "aptNumber", "dependentLocality", "extendedAddress",
+ * "postalCode", "extendedPostalCode", "addressLocality", "addressRegion", "addressCountry".
+ *
+ * <p>
+ *
+ * "phoneNumber" is the group hint for hints all phone number related fields: "phoneNumber",
+ * "phoneNumberDevice", "phoneNational", "phoneCountryCode".
+ *
+ * <p>
+ *
+ * "personName" is the group hint for hints all name related fields: "personName",
+ * "personFamilyName", "personGivenName", "personMiddleName", "personMiddleInitial",
+ * "personNamePrefix", "personNameSuffix" .
+ *
+ * <p>
+ *
+ * "birthDateFull" is the group hint for hints containing birthday related fields:
+ * "birthDateFull", "birthDateMonth", "birthDateYear",
+ *
+ * @hide
+ */
+ private final @NonNull Set<String> mGroupHints;
+
+ /**
+ * Autofill id of the detected field.
+ */
+ public @NonNull AutofillId getAutofillId() {
+ return mAutofillId;
+ }
+
+ /**
+ * Detected fields types represented as autofill hints.
+ *
+ * A particular field can be detected as multiple types. For eg: A sign-in field may take in a
+ * username, an email address or a phone number. In such cases, it should be detected as
+ * "username", "emailAddress" and "phoneNumber"
+ *
+ * The value of these hints are contained in androidx.autofill.HintConstants
+ */
+ public @NonNull Set<String> getHints() {
+ return mHints;
+ }
+
+ /**
+ * Group hints are the hints that may represent the group of related hints (including
+ * themselves). The value of these group hints are contained in androidx.autofill.HintConstants
+ *
+ * <p>
+ *
+ * "creditCardNumber" is the group hint for hints containing credit card related fields:
+ * "creditCardNumber", "creditCardExpirationDate", "creditCardExpirationDay",
+ * "creditCardExpirationMonth", "creditCardExpirationYear", "creditCardSecurityCode",
+ *
+ * <p>
+ *
+ * "postalAddress" is the group hint for hints all postal address related fields:
+ * "postalAddress", "streetAddress", "aptNumber", "dependentLocality", "extendedAddress",
+ * "postalCode", "extendedPostalCode", "addressLocality", "addressRegion", "addressCountry".
+ *
+ * <p>
+ *
+ * "phoneNumber" is the group hint for hints all phone number related fields: "phoneNumber",
+ * "phoneNumberDevice", "phoneNational", "phoneCountryCode".
+ *
+ * <p>
+ *
+ * "personName" is the group hint for hints all name related fields: "personName",
+ * "personFamilyName", "personGivenName", "personMiddleName", "personMiddleInitial",
+ * "personNamePrefix", "personNameSuffix" .
+ *
+ * <p>
+ *
+ * "birthDateFull" is the group hint for hints containing birthday related fields:
+ * "birthDateFull", "birthDateMonth", "birthDateYear",
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Set<String> getGroupHints() {
+ return mGroupHints;
+ }
+
+ static Set<String> unparcelHints(Parcel in) {
+ List<String> hints = new java.util.ArrayList<>();
+ in.readStringList(hints);
+ return new ArraySet<>(hints);
+ }
+
+ void parcelHints(Parcel dest, int flags) {
+ dest.writeStringList(new ArrayList<>(mHints));
+ }
+
+ static Set<String> unparcelGroupHints(Parcel in) {
+ List<String> groupHints = new java.util.ArrayList<>();
+ in.readStringList(groupHints);
+ return new ArraySet<>(groupHints);
+ }
+
+ void parcelGroupHints(Parcel dest, int flags) {
+ dest.writeStringList(new ArrayList<>(mGroupHints));
+ }
+
+ /**
+ * Creates a new FieldClassification.
+ *
+ * @param autofillId
+ * Autofill id of the detected field
+ * @param hints
+ * Detected fields types represented as autofill hints.
+ * A particular field can be detected as multiple types. For eg: A sign-in field may take in
+ * a username, an email address or a phone number. In such cases, it should be detected as
+ * "username", "emailAddress" and "phoneNumber"
+ */
+ public FieldClassification(
+ @NonNull AutofillId autofillId,
+ @NonNull Set<String> hints) {
+ this(autofillId, hints, new ArraySet<>());
+ }
+
+ /**
+ * Creates a new FieldClassification.
+ *
+ * @param autofillId Autofill id of the detected field
+ * @param hints Detected fields types represented as autofill hints A particular field can be
+ * detected as multiple types. For eg: A sign-in field may take in a username, an email
+ * address or a phone number. In such cases, it should be detected as "username",
+ * "emailAddress" and "phoneNumber"
+ * @param groupHints Hints that may represent the group of related hints (including themselves).
+ * The value of these group hints are contained in androidx.autofill.HintConstants.
+ * See {@link #getGroupHints()} for more details
+ * @hide
+ */
+ @SystemApi
+ @DataClass.Generated.Member
+ public FieldClassification(
+ @NonNull AutofillId autofillId,
+ @NonNull Set<String> hints,
+ @NonNull Set<String> groupHints) {
+ this.mAutofillId = autofillId;
+// com.android.internal.util.AnnotationValidations.validate(
+// NonNull.class, null, mAutofillId);
+ this.mHints = hints;
+// com.android.internal.util.AnnotationValidations.validate(
+// NonNull.class, null, mHints);
+ this.mGroupHints = groupHints;
+// com.android.internal.util.AnnotationValidations.validate(
+// NonNull.class, null, mGroupHints);
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/assist/classification/FieldClassification.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "FieldClassification { " +
+ "autofillId = " + mAutofillId + ", " +
+ "hints = " + mHints + ", " +
+ "groupHints = " + mGroupHints +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mAutofillId, flags);
+ parcelHints(dest, flags);
+ parcelGroupHints(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ FieldClassification(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ AutofillId autofillId = (AutofillId) in.readTypedObject(AutofillId.CREATOR);
+ Set<String> hints = unparcelHints(in);
+ Set<String> groupHints = unparcelGroupHints(in);
+
+ this.mAutofillId = autofillId;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAutofillId);
+ this.mHints = hints;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHints);
+ this.mGroupHints = groupHints;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mGroupHints);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<FieldClassification> CREATOR
+ = new Parcelable.Creator<FieldClassification>() {
+ @Override
+ public FieldClassification[] newArray(int size) {
+ return new FieldClassification[size];
+ }
+
+ @Override
+ public FieldClassification createFromParcel(@NonNull Parcel in) {
+ return new FieldClassification(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1675320464097L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/assist/classification/FieldClassification.java",
+ inputSignatures = "private final @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mHints\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mGroupHints\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\npublic @android.annotation.NonNull java.util.Set<java.lang.String> getHints()\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.Set<java.lang.String> getGroupHints()\nstatic java.util.Set<java.lang.String> unparcelHints(android.os.Parcel)\n void parcelHints(android.os.Parcel,int)\nstatic java.util.Set<java.lang.String> unparcelGroupHints(android.os.Parcel)\n void parcelGroupHints(android.os.Parcel,int)\nclass FieldClassification extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/packages/SystemUI/res/values-television/strings.xml b/core/java/android/service/assist/classification/FieldClassificationRequest.aidl
similarity index 60%
rename from packages/SystemUI/res/values-television/strings.xml
rename to core/java/android/service/assist/classification/FieldClassificationRequest.aidl
index 86106e6..740c5cb9 100644
--- a/packages/SystemUI/res/values-television/strings.xml
+++ b/core/java/android/service/assist/classification/FieldClassificationRequest.aidl
@@ -1,7 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
/**
- * Copyright (c) 2022, The Android Open Source Project
+ * Copyright (c) 2023, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +13,7 @@
* 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">
- <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more_url" translatable="false"></string>
-</resources>
\ No newline at end of file
+
+package android.service.assist.classification;
+
+parcelable FieldClassificationRequest;
\ No newline at end of file
diff --git a/core/java/android/service/assist/classification/FieldClassificationRequest.java b/core/java/android/service/assist/classification/FieldClassificationRequest.java
new file mode 100644
index 0000000..0afcca9
--- /dev/null
+++ b/core/java/android/service/assist/classification/FieldClassificationRequest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.assist.AssistStructure;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Represents a request to detect fields on an activity.
+ * @hide
+ */
+@SystemApi
+@DataClass(
+ genToString = true
+)
+public final class FieldClassificationRequest implements Parcelable {
+ private final @NonNull AssistStructure mAssistStructure;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/assist/classification/FieldClassificationRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public FieldClassificationRequest(
+ @NonNull AssistStructure assistStructure) {
+ this.mAssistStructure = assistStructure;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAssistStructure);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull AssistStructure getAssistStructure() {
+ return mAssistStructure;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "FieldClassificationRequest { " +
+ "assistStructure = " + mAssistStructure +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mAssistStructure, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ FieldClassificationRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ AssistStructure assistStructure = (AssistStructure) in.readTypedObject(AssistStructure.CREATOR);
+
+ this.mAssistStructure = assistStructure;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAssistStructure);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<FieldClassificationRequest> CREATOR
+ = new Parcelable.Creator<FieldClassificationRequest>() {
+ @Override
+ public FieldClassificationRequest[] newArray(int size) {
+ return new FieldClassificationRequest[size];
+ }
+
+ @Override
+ public FieldClassificationRequest createFromParcel(@NonNull Parcel in) {
+ return new FieldClassificationRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1675320491692L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/assist/classification/FieldClassificationRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull android.app.assist.AssistStructure mAssistStructure\nclass FieldClassificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/assist/classification/FieldClassificationResponse.aidl b/core/java/android/service/assist/classification/FieldClassificationResponse.aidl
new file mode 100644
index 0000000..1b0de85
--- /dev/null
+++ b/core/java/android/service/assist/classification/FieldClassificationResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+parcelable FieldClassificationResponse;
diff --git a/core/java/android/service/assist/classification/FieldClassificationResponse.java b/core/java/android/service/assist/classification/FieldClassificationResponse.java
new file mode 100644
index 0000000..faa9488
--- /dev/null
+++ b/core/java/android/service/assist/classification/FieldClassificationResponse.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * Represents a response from detection service.
+ * @hide
+ */
+@SystemApi
+@DataClass(
+ genToString = true
+)
+public final class FieldClassificationResponse implements Parcelable {
+
+ /**
+ * List of classified fields
+ */
+ private final @NonNull Set<FieldClassification> mClassifications;
+
+ static Set<FieldClassification> unparcelClassifications(Parcel in) {
+ List<FieldClassification> detections = new java.util.ArrayList<>();
+ in.readParcelableList(
+ detections, FieldClassification.class.getClassLoader(), FieldClassification.class);
+ return new ArraySet<>(detections);
+ }
+
+ void parcelClassifications(Parcel dest, int flags) {
+ dest.writeParcelableList(new ArrayList<>(mClassifications), flags);
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/assist/classification/FieldClassificationResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new FieldClassificationResponse.
+ *
+ * @param classifications
+ * List of classified fields
+ */
+ @DataClass.Generated.Member
+ public FieldClassificationResponse(
+ @NonNull Set<FieldClassification> classifications) {
+ this.mClassifications = classifications;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mClassifications);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * List of classified fields
+ */
+ @DataClass.Generated.Member
+ public @NonNull Set<FieldClassification> getClassifications() {
+ return mClassifications;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "FieldClassificationResponse { " +
+ "classifications = " + mClassifications +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ parcelClassifications(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ FieldClassificationResponse(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Set<FieldClassification> classifications = unparcelClassifications(in);
+
+ this.mClassifications = classifications;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mClassifications);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<FieldClassificationResponse> CREATOR
+ = new Parcelable.Creator<FieldClassificationResponse>() {
+ @Override
+ public FieldClassificationResponse[] newArray(int size) {
+ return new FieldClassificationResponse[size];
+ }
+
+ @Override
+ public FieldClassificationResponse createFromParcel(@NonNull Parcel in) {
+ return new FieldClassificationResponse(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1675320458276L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/assist/classification/FieldClassificationResponse.java",
+ inputSignatures = "private final @android.annotation.NonNull java.util.Set<android.service.assist.classification.FieldClassification> mClassifications\nstatic java.util.Set<android.service.assist.classification.FieldClassification> unparcelClassifications(android.os.Parcel)\n void parcelClassifications(android.os.Parcel,int)\nclass FieldClassificationResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/assist/classification/FieldClassificationService.java b/core/java/android/service/assist/classification/FieldClassificationService.java
new file mode 100644
index 0000000..abffdbf
--- /dev/null
+++ b/core/java/android/service/assist/classification/FieldClassificationService.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.BaseBundle;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A service using {@link android.app.assist.AssistStructure} to detect fields on the screen.
+ * Service may use classifiers to look at the un-stripped AssistStructure to make informed decision
+ * and classify the fields.
+ *
+ * Currently, it's used to detect the field types for the Autofill Framework to provide relevant
+ * autofill suggestions to the user.
+ *
+ *
+ * The methods are invoked on the binder threads.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class FieldClassificationService extends Service {
+
+ private static final String TAG = FieldClassificationService.class.getSimpleName();
+
+ static boolean sDebug = Build.IS_USER ? false : true;
+ static boolean sVerbose = false;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_FIELD_CLASSIFICATION_SERVICE} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.assist.classification.FieldClassificationService";
+
+ // Used for metrics / debug only
+ private ComponentName mServiceComponentName;
+
+ private final class FieldClassificationServiceImpl
+ extends IFieldClassificationService.Stub {
+
+ @Override
+ public void onConnected(boolean debug, boolean verbose) {
+ handleOnConnected(debug, verbose);
+ }
+
+ @Override
+ public void onDisconnected() {
+ handleOnDisconnected();
+ }
+
+ @Override
+ public void onFieldClassificationRequest(
+ FieldClassificationRequest request, IFieldClassificationCallback callback) {
+ handleOnClassificationRequest(request, callback);
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ BaseBundle.setShouldDefuse(true);
+ }
+
+ /** @hide */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ mServiceComponentName = intent.getComponent();
+ return new FieldClassificationServiceImpl().asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ /**
+ * Called when the Android system connects to service.
+ *
+ * <p>You should generally do initialization here rather than in {@link #onCreate}.
+ */
+ public void onConnected() {
+ }
+
+ /**
+ * Requests the service to handle field classification request.
+ * @param cancellationSignal signal for observing cancellation requests. The system will use
+ * this to notify you that the detection result is no longer needed and the service should
+ * stop handling this detection request in order to save resources.
+ * @param outcomeReceiver object used to notify the result of the request. Service <b>must</b>
+ * call {@link OutcomeReceiver<>#onResult(FieldClassificationResponse)}.
+ */
+ public abstract void onClassificationRequest(
+ @NonNull FieldClassificationRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull OutcomeReceiver<FieldClassificationResponse, Exception> outcomeReceiver);
+
+ /**
+ * Called when the Android system disconnects from the service.
+ *
+ * <p> At this point this service may no longer be an active
+ * {@link FieldClassificationService}.
+ */
+ public void onDisconnected() {
+ }
+
+ private void handleOnConnected(boolean debug, boolean verbose) {
+ if (sDebug || debug) {
+ Log.d(TAG, "handleOnConnected(): debug=" + debug + ", verbose=" + verbose);
+ }
+ sDebug = debug;
+ sVerbose = verbose;
+ onConnected();
+ }
+
+ private void handleOnDisconnected() {
+ onDisconnected();
+ }
+
+ private void handleOnClassificationRequest(
+ FieldClassificationRequest request, @NonNull IFieldClassificationCallback callback) {
+
+ final ICancellationSignal transport = CancellationSignal.createTransport();
+ final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
+ onClassificationRequest(
+ request,
+ cancellationSignal,
+ new OutcomeReceiver<FieldClassificationResponse, Exception>() {
+ @Override
+ public void onResult(FieldClassificationResponse result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public void onError(Exception e) {
+ try {
+ callback.onFailure();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+ });
+ }
+}
+
diff --git a/core/java/android/service/assist/classification/IFieldClassificationCallback.aidl b/core/java/android/service/assist/classification/IFieldClassificationCallback.aidl
new file mode 100644
index 0000000..ca9e939
--- /dev/null
+++ b/core/java/android/service/assist/classification/IFieldClassificationCallback.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+import android.os.Bundle;
+import android.os.ICancellationSignal;
+
+import android.service.assist.classification.FieldClassificationResponse;
+
+import java.util.List;
+
+/**
+ * Interface to receive the result of an autofill request.
+ *
+ * @hide
+ */
+interface IFieldClassificationCallback {
+
+ void onCancellable(in ICancellationSignal cancellation);
+
+ void onSuccess(in FieldClassificationResponse response);
+
+ void onFailure();
+
+ boolean isCompleted();
+
+ void cancel();
+}
diff --git a/core/java/android/service/assist/classification/IFieldClassificationService.aidl b/core/java/android/service/assist/classification/IFieldClassificationService.aidl
new file mode 100644
index 0000000..a93688d
--- /dev/null
+++ b/core/java/android/service/assist/classification/IFieldClassificationService.aidl
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.assist.classification;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.service.assist.classification.IFieldClassificationCallback;
+import android.service.assist.classification.FieldClassificationRequest;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import java.util.List;
+/**
+ * Interface from the system to an Autofill classification service.
+ *
+ * @hide
+ */
+oneway interface IFieldClassificationService {
+ void onConnected(boolean debug, boolean verbose);
+ void onDisconnected();
+ void onFieldClassificationRequest(
+ in FieldClassificationRequest request, in IFieldClassificationCallback callback);
+}
diff --git a/core/java/android/service/credentials/GetCredentialRequest.java b/core/java/android/service/credentials/GetCredentialRequest.java
index e808ace..7cdccc6 100644
--- a/core/java/android/service/credentials/GetCredentialRequest.java
+++ b/core/java/android/service/credentials/GetCredentialRequest.java
@@ -90,7 +90,7 @@
* Returns the parameters needed to return a given type of credential.
*/
@NonNull
- public CredentialOption getGetCredentialOption() {
+ public CredentialOption getCredentialOption() {
return mCredentialOption;
}
}
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 59a0c2e..ec44100 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -1,3 +1,7 @@
# Bug component: 533220
include /core/java/android/app/assist/OWNERS
+
+# The owner here should not be assist owner
+liangyuchen@google.com
+tuanng@google.com
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index d77bbcc..c18adfc 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -67,24 +67,6 @@
}
/**
- * Gets binary measurements of all installed APEXs, each packed in a Bundle.
- * @return A List of {@link android.os.Bundle}s with the following keys:
- * {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO}
- * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM}
- * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST}
- */
- // TODO(b/259422958): Fix static constants referenced here - should be defined here
- @NonNull
- public List getApexInfo() {
- try {
- Slog.d(TAG, "Calling backend's getApexInfo()");
- return mService.getApexInfo();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Collects the APEX information on the device.
*
* @param includeTestOnly Whether to include test only data in the returned ApexInfo.
@@ -116,4 +98,21 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Collects the silent installed MBA information on the device.
+ *
+ * @return A List containing the MBA info of silent installed.
+ * @hide
+ */
+ @NonNull
+ public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
+ Bundle packagesToSkip) {
+ try {
+ Slog.d(TAG, "Calling backend's collectAllSilentInstalledMbaInfo()");
+ return mService.collectAllSilentInstalledMbaInfo(packagesToSkip);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index 3e7c67e..f20767b 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -27,6 +27,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -245,4 +246,23 @@
throw new IllegalArgumentException("Unknown rotation: " + rotation);
}
}
+
+ /**
+ * Reverses the rotation direction around the Z axis. Note that this method assumes all
+ * rotations are relative to {@link Surface.ROTATION_0}.
+ *
+ * @param rotation the original rotation.
+ * @return the new rotation that should be applied.
+ */
+ @Surface.Rotation
+ public static int reverseRotationDirectionAroundZAxis(@Surface.Rotation int rotation) {
+ // Flipping 270 and 90 has the same effect as changing the direction which rotation is
+ // applied.
+ if (rotation == Surface.ROTATION_90) {
+ rotation = Surface.ROTATION_270;
+ } else if (rotation == Surface.ROTATION_270) {
+ rotation = Surface.ROTATION_90;
+ }
+ return rotation;
+ }
}
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 26fda34..edce001 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -27,6 +27,8 @@
import dalvik.annotation.optimization.FastNative;
+import libcore.util.NativeAllocationRegistry;
+
import java.lang.ref.WeakReference;
/**
@@ -81,11 +83,17 @@
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
- private static native void nativeDispose(long receiverPtr);
+ private static native long nativeGetDisplayEventReceiverFinalizer();
@FastNative
private static native void nativeScheduleVsync(long receiverPtr);
private static native VsyncEventData nativeGetLatestVsyncEventData(long receiverPtr);
+ private static final NativeAllocationRegistry sNativeAllocationRegistry =
+ NativeAllocationRegistry.createMalloced(
+ DisplayEventReceiver.class.getClassLoader(),
+ nativeGetDisplayEventReceiverFinalizer());
+ private Runnable mFreeNativeResources;
+
/**
* Creates a display event receiver.
*
@@ -118,27 +126,16 @@
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource, eventRegistration, layerHandle);
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- dispose(true);
- } finally {
- super.finalize();
- }
+ mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this,
+ mReceiverPtr);
}
/**
* Disposes the receiver.
*/
public void dispose() {
- dispose(false);
- }
-
- private void dispose(boolean finalized) {
if (mReceiverPtr != 0) {
- nativeDispose(mReceiverPtr);
+ mFreeNativeResources.run();
mReceiverPtr = 0;
}
mMessageQueue = null;
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index f4fac0b..3a02c48 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -765,7 +765,7 @@
sb.append(name);
sb.append("\", displayId ");
sb.append(displayId);
- sb.append("\", displayGroupId ");
+ sb.append(", displayGroupId ");
sb.append(displayGroupId);
sb.append(flagsToString(flags));
sb.append(", real ");
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 18e7e05..8663013 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1386,7 +1386,6 @@
synchronized (mChoreographerLock) {
if (mChoreographer != null) {
mChoreographer.invalidate();
- // TODO(b/266121235): Use NativeAllocationRegistry to clean up Choreographer.
mChoreographer = null;
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 06dab15..a5693f3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8919,8 +8919,7 @@
* @hide
*/
@UnsupportedAppUsage
- @TestApi
- public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
+ public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8928,7 +8927,6 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
- mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15966,8 +15964,7 @@
* @hide
*/
@UnsupportedAppUsage
- @TestApi
- public void getWindowDisplayFrame(@NonNull Rect outRect) {
+ public void getWindowDisplayFrame(Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -26176,11 +26173,7 @@
getLocationInWindow(outLocation);
final AttachInfo info = mAttachInfo;
-
- // Need to offset the outLocation with the window bounds, but only if "Sandboxing View
- // Bounds APIs" is disabled. If this override is enabled, it sandboxes {@link outLocation}
- // within activity bounds.
- if (info != null && !info.mViewRootImpl.isViewBoundsSandboxingEnabled()) {
+ if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 872b4f9..d603de2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,7 +16,6 @@
package android.view;
-import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -80,7 +79,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -100,7 +98,6 @@
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
-import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -772,7 +769,12 @@
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
- private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ /**
+ * The resolved pointer icon type requested by this window.
+ * A null value indicates the resolved pointer icon has not yet been calculated.
+ */
+ @Nullable
+ private Integer mPointerIconType = null;
private PointerIcon mCustomPointerIcon = null;
/**
@@ -892,15 +894,6 @@
private boolean mRelayoutRequested;
- /**
- * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getWindowDisplayFrame} and
- * {@link android.view.View#getWindowVisibleDisplayFrame}
- * within Activity bounds is enabled for the current application.
- */
- private final boolean mViewBoundsSandboxingEnabled;
-
private int mLastTransformHint = Integer.MIN_VALUE;
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
@@ -992,8 +985,6 @@
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
- mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
-
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -6905,13 +6896,13 @@
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
// Other apps or the window manager may change the icon type outside of
// this app, therefore the icon type has to be reset on enter/exit event.
- mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ mPointerIconType = null;
}
if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
if (!updatePointerIcon(event) &&
event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
- mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ mPointerIconType = null;
}
}
}
@@ -6950,7 +6941,7 @@
}
private void resetPointerIcon(MotionEvent event) {
- mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ mPointerIconType = null;
updatePointerIcon(event);
}
@@ -6980,9 +6971,9 @@
}
final int pointerType = (pointerIcon != null) ?
- pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;
+ pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
- if (mPointerIconType != pointerType) {
+ if (mPointerIconType == null || mPointerIconType != pointerType) {
mPointerIconType = pointerType;
mCustomPointerIcon = null;
if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
@@ -8570,9 +8561,6 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
- // Apply sandboxing here (in getter) due to possible layout updates on the client after
- // {@link #mTmpFrames.displayFrame} is received from the server.
- applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8589,60 +8577,6 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
- // Apply sandboxing here (in getter) due to possible layout updates on the client after
- // {@link #mTmpFrames.displayFrame} is received from the server.
- applyViewBoundsSandboxingIfNeeded(outFrame);
- }
-
- /**
- * Offset outRect to make it sandboxed within Window's bounds.
- *
- * <p>This is used by {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.ViewRootImpl#getDisplayFrame} and
- * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
- * {@link android.view.View#getWindowDisplayFrame} and
- * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
- * {@link android.view.ViewDebug#captureLayers} for debugging.
- */
- void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
- if (isViewBoundsSandboxingEnabled()) {
- inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
- }
- }
-
- /**
- * Whether the sanboxing of the {@link android.view.View} APIs is enabled.
- *
- * <p>This is called by {@link #applyViewBoundsSandboxingIfNeeded} and
- * {@link android.view.View#getLocationOnScreen} to check if there is a need to add
- * {@link android.view.View.AttachInfo.mWindowLeft} and
- * {@link android.view.View.AttachInfo.mWindowTop} offsets.
- */
- boolean isViewBoundsSandboxingEnabled() {
- return mViewBoundsSandboxingEnabled;
- }
-
- private boolean getViewBoundsSandboxingEnabled() {
- if (!CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
- // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
- return false;
- }
-
- // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
- try {
- final List<PackageManager.Property> properties = mContext.getPackageManager()
- .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
-
- final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
- if (isOptedOut) {
- // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
- return false;
- }
- } catch (RuntimeException e) {
- // remote exception.
- }
-
- return true;
}
/**
@@ -8839,6 +8773,8 @@
mInsetsController.dump(prefix, writer);
+ mOnBackInvokedDispatcher.dump(prefix, writer);
+
writer.println(prefix + "View Hierarchy:");
dumpViewHierarchy(innerPrefix, writer, mView);
}
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 5ec5219..3b8298e 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -17,9 +17,9 @@
package android.view;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -109,14 +109,6 @@
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
// the cutout safe zone.
final Rect displayFrame = state.getDisplayFrame();
- final InsetsSource statusBarSource = state.peekSource(ITYPE_STATUS_BAR);
- if (statusBarSource != null && displayCutoutSafe.top > displayFrame.top) {
- // Make sure that the zone we're avoiding for the cutout is at least as tall as the
- // status bar; otherwise fullscreen apps will end up cutting halfway into the status
- // bar.
- displayCutoutSafeExceptMaybeBars.top =
- Math.max(statusBarSource.getFrame().bottom, displayCutoutSafe.top);
- }
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
if (displayFrame.width() < displayFrame.height()) {
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
@@ -131,7 +123,7 @@
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
- displayFrame, WindowInsets.Type.systemBars(), requestedVisibleTypes);
+ displayFrame, systemBars(), requestedVisibleTypes);
if (systemBarsInsets.left > 0) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
@@ -145,12 +137,11 @@
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
- if (type == TYPE_INPUT_METHOD) {
- final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR);
- if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) {
- // The IME can always extend under the bottom cutout if the navbar is there.
- displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
- }
+ if (type == TYPE_INPUT_METHOD
+ && displayCutoutSafeExceptMaybeBars.bottom != MAX_Y
+ && state.calculateInsets(displayFrame, navigationBars(), true).bottom > 0) {
+ // The IME can always extend under the bottom cutout if the navbar is there.
+ displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3d3f4f6..0f68cd0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -855,42 +855,6 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that it needs to be opted-out from the
- * compatibility treatment that sandboxes {@link android.view.View} API.
- *
- * <p>The treatment can be enabled by device manufacturers for applications which misuse
- * {@link android.view.View} APIs by expecting that
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View#getWindowDisplayFrame}
- * return coordinates as if an activity is positioned in the top-left corner of the screen, with
- * left coordinate equal to 0. This may not be the case for applications in multi-window and in
- * letterbox modes.
- *
- * <p>Setting this property to {@code false} informs the system that the application must be
- * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
- * if the device manufacturer has opted the app into the treatment.
- *
- * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
- *
- * <p><b>Syntax:</b>
- * <pre>
- * <application>
- * <property
- * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
- * android:value="false"/>
- * </application>
- * </pre>
- *
- * @hide
- */
- // TODO(b/263984287): Make this public API.
- String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
- "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
-
- /**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index a0bd7f7..34b75a4 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -211,6 +211,12 @@
IOnBackInvokedCallback getIOnBackInvokedCallback() {
return mIOnBackInvokedCallback;
}
+
+ @Override
+ public String toString() {
+ return "ImeCallback=ImeOnBackInvokedCallback@" + mId
+ + " Callback=" + mIOnBackInvokedCallback;
+ }
}
/**
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 09d8b0f..56c05b2 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -180,16 +180,7 @@
return;
}
clearCallbacksOnDispatcher();
- if (actualDispatcher instanceof ProxyOnBackInvokedDispatcher) {
- // We don't want to nest ProxyDispatchers, so if we are given on, we unwrap its
- // actual dispatcher.
- // This can happen when an Activity is recreated but the Window is preserved (e.g.
- // when going from split-screen back to single screen)
- mActualDispatcher =
- ((ProxyOnBackInvokedDispatcher) actualDispatcher).mActualDispatcher;
- } else {
- mActualDispatcher = actualDispatcher;
- }
+ mActualDispatcher = actualDispatcher;
transferCallbacksToDispatcher();
}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 7a5510c..d34ece9 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -29,6 +29,7 @@
import android.view.IWindow;
import android.view.IWindowSession;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -232,6 +233,26 @@
return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext());
}
+ /**
+ * Dump information about this WindowOnBackInvokedDispatcher
+ * @param prefix the prefix that will be prepended to each line of the produced output
+ * @param writer the writer that will receive the resulting text
+ */
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ writer.println(prefix + "WindowOnBackDispatcher:");
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "<None>");
+ return;
+ }
+
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
+
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
new file mode 100644
index 0000000..94c230b
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.accessibility.common;
+
+/**
+ * Collection of common constants for accessibility shortcut.
+ */
+public final class MagnificationConstants {
+ private MagnificationConstants() {}
+
+ /**
+ * The min value for the magnification persisted scale. We assume if the scale is lower than
+ * the min value, there will be no obvious magnification effect.
+ */
+ public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
new file mode 100644
index 0000000..f724e55
--- /dev/null
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.config.sysui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provides a central definition of debug SystemUI's SystemProperties flags, and their defaults.
+ *
+ * The main feature of this class is that it encodes a system-wide default for each flag which can
+ * be updated by engineers with a single-line CL.
+ *
+ * NOTE: Because flag values returned by this class are not cached, it is important that developers
+ * understand the intricacies of changing values and how that applies to their own code.
+ * Generally, the best practice is to set the property, and then restart the device so that any
+ * processes with stale state can be updated. However, if your code has no state derived from the
+ * flag value and queries it any time behavior is relevant, then it may be safe to change the flag
+ * and not immediately reboot.
+ *
+ * To enable flags in debuggable builds, use the following commands:
+ *
+ * $ adb shell setprop persist.sysui.whatever_the_flag true
+ * $ adb reboot
+ *
+ * @hide
+ */
+public class SystemUiSystemPropertiesFlags {
+
+ /** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
+ public interface FlagResolver {
+ /** Is the flag enabled? */
+ boolean isEnabled(Flag flag);
+ }
+
+ /** The primary, immutable resolver returned by getResolver() */
+ private static final FlagResolver
+ MAIN_RESOLVER =
+ Build.IS_DEBUGGABLE ? new DebugResolver() : new ProdResolver();
+
+ /**
+ * On debuggable builds, this can be set to override the resolver returned by getResolver().
+ * This can be useful to override flags when testing components that do not allow injecting the
+ * SystemUiPropertiesFlags resolver they use.
+ * Always set this to null when tests tear down.
+ */
+ @VisibleForTesting
+ public static FlagResolver TEST_RESOLVER = null;
+
+ /** Get the resolver for this device configuration. */
+ public static FlagResolver getResolver() {
+ if (Build.IS_DEBUGGABLE && TEST_RESOLVER != null) {
+ Log.i("SystemUiSystemPropertiesFlags", "Returning debug resolver " + TEST_RESOLVER);
+ return TEST_RESOLVER;
+ }
+ return MAIN_RESOLVER;
+ }
+
+ /** The teamfood flag allows multiple features to be opted into at once. */
+ public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
+
+ /**
+ * Flags related to notification features
+ */
+ public static final class NotificationFlags {
+
+ /**
+ * FOR DEVELOPMENT / TESTING ONLY!!!
+ * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
+ */
+ public static final Flag FSI_FORCE_DEMOTE =
+ devFlag("persist.sysui.notification.fsi_force_demote");
+
+ /** Gating the ability for users to dismiss ongoing event notifications */
+ public static final Flag ALLOW_DISMISS_ONGOING =
+ devFlag("persist.sysui.notification.ongoing_dismissal");
+
+ /** Gating the redaction of OTP notifications on the lockscreen */
+ public static final Flag OTP_REDACTION =
+ devFlag("persist.sysui.notification.otp_redaction");
+
+ }
+
+ //// == Everything below this line is the implementation == ////
+
+ /**
+ * Creates a flag that is enabled by default in debuggable builds.
+ * It can be enabled by setting this flag's SystemProperty to 1.
+ *
+ * This flag is ALWAYS disabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag devFlag(String name) {
+ return new Flag(name, false, null);
+ }
+
+ /**
+ * Creates a flag that is disabled by default in debuggable builds.
+ * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
+ * If this flag's SystemProperty is not set, the flag can be enabled by setting the
+ * TEAMFOOD flag's SystemProperty to 1.
+ *
+ * This flag is ALWAYS disabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag teamfoodFlag(String name) {
+ return new Flag(name, false, TEAMFOOD);
+ }
+
+ /**
+ * Creates a flag that is enabled by default in debuggable builds.
+ * It can be enabled by setting this flag's SystemProperty to 0.
+ *
+ * This flag is ALWAYS enabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag releasedFlag(String name) {
+ return new Flag(name, true, null);
+ }
+
+ /** Represents a developer-switchable gate for a feature. */
+ public static final class Flag {
+ public final String mSysPropKey;
+ public final boolean mDefaultValue;
+ @Nullable
+ public final Flag mDebugDefault;
+
+ /** constructs a new flag. only visible for testing the class */
+ @VisibleForTesting
+ public Flag(@NonNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault) {
+ mSysPropKey = sysPropKey;
+ mDefaultValue = defaultValue;
+ mDebugDefault = debugDefault;
+ }
+ }
+
+ /** Implementation of the interface used in release builds. */
+ @VisibleForTesting
+ public static final class ProdResolver implements
+ FlagResolver {
+ @Override
+ public boolean isEnabled(Flag flag) {
+ return flag.mDefaultValue;
+ }
+ }
+
+ /** Implementation of the interface used in debuggable builds. */
+ @VisibleForTesting
+ public static class DebugResolver implements FlagResolver {
+ @Override
+ public final boolean isEnabled(Flag flag) {
+ if (flag.mDebugDefault == null) {
+ return getBoolean(flag.mSysPropKey, flag.mDefaultValue);
+ }
+ return getBoolean(flag.mSysPropKey, isEnabled(flag.mDebugDefault));
+ }
+
+ /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+ @VisibleForTesting
+ public boolean getBoolean(String key, boolean defaultValue) {
+ return SystemProperties.getBoolean(key, defaultValue);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index e782aa7..c8340ac 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -28,8 +28,6 @@
interface IBinaryTransparencyService {
String getSignedImageInfo();
- List getApexInfo();
-
void recordMeasurementsForAllPackages();
parcelable ApexInfo {
@@ -60,4 +58,5 @@
/** Test only */
List<ApexInfo> collectAllApexInfo(boolean includeTestOnly);
List<AppInfo> collectAllUpdatedPreloadInfo(in Bundle packagesToSkip);
+ List<AppInfo> collectAllSilentInstalledMbaInfo(in Bundle packagesToSkip);
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index c6aca44..b63041b 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -378,8 +378,12 @@
// window, as we'll be skipping the addView in handleResumeActivity(), and
// the token will not be updated as for a new window.
getAttributes().token = preservedWindow.getAttributes().token;
- mProxyOnBackInvokedDispatcher.setActualDispatcher(
- preservedWindow.getOnBackInvokedDispatcher());
+ final ViewRootImpl viewRoot = mDecor.getViewRootImpl();
+ if (viewRoot != null) {
+ // Clear the old callbacks and attach to the new window.
+ viewRoot.getOnBackInvokedDispatcher().clear();
+ onViewRootImplSet(viewRoot);
+ }
}
// Even though the device doesn't support picture-in-picture mode,
// an user can force using it through developer options.
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 786941f..74a9d16 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -81,6 +81,15 @@
}
}
+ /** Enables fs-verity for an open file without signature. */
+ public static void setUpFsverity(int fd) throws IOException {
+ int errno = enableFsverityForFdNative(fd);
+ if (errno != 0) {
+ throw new IOException("Failed to enable fs-verity on FD(" + fd + "): "
+ + Os.strerror(errno));
+ }
+ }
+
/** Returns whether the file has fs-verity enabled. */
public static boolean hasFsverity(@NonNull String filePath) {
int retval = statxForFsverityNative(filePath);
@@ -211,6 +220,7 @@
}
private static native int enableFsverityNative(@NonNull String filePath);
+ private static native int enableFsverityForFdNative(int fd);
private static native int measureFsverityNative(@NonNull String filePath,
@NonNull byte[] digest);
private static native int statxForFsverityNative(@NonNull String filePath);
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 8855b78..b09a9c3 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -240,13 +240,15 @@
return reinterpret_cast<jlong>(receiver.get());
}
-static void nativeDispose(JNIEnv* env, jclass clazz, jlong receiverPtr) {
- NativeDisplayEventReceiver* receiver =
- reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
+static void release(NativeDisplayEventReceiver* receiver) {
receiver->dispose();
receiver->decStrong(gDisplayEventReceiverClassInfo.clazz); // drop reference held by the object
}
+static jlong nativeGetDisplayEventReceiverFinalizer(JNIEnv*, jclass) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&release));
+}
+
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
sp<NativeDisplayEventReceiver> receiver =
reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
@@ -274,7 +276,8 @@
/* name, signature, funcPtr */
{"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J",
(void*)nativeInit},
- {"nativeDispose", "(J)V", (void*)nativeDispose},
+ {"nativeGetDisplayEventReceiverFinalizer", "()J",
+ (void*)nativeGetDisplayEventReceiverFinalizer},
// @FastNative
{"nativeScheduleVsync", "(J)V", (void*)nativeScheduleVsync},
{"nativeGetLatestVsyncEventData", "(J)Landroid/view/DisplayEventReceiver$VsyncEventData;",
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index 3e5689b..4a9e2d4 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -38,13 +38,8 @@
namespace {
-int enableFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
- ScopedUtfChars path(env, filePath);
- if (path.c_str() == nullptr) {
- return EINVAL;
- }
- ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
- if (rfd.get() < 0) {
+int enableFsverityForFd(JNIEnv *env, jobject clazz, jint fd) {
+ if (fd < 0) {
return errno;
}
@@ -55,12 +50,21 @@
arg.salt_size = 0;
arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
- if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
+ if (ioctl(fd, FS_IOC_ENABLE_VERITY, &arg) < 0) {
return errno;
}
return 0;
}
+int enableFsverity(JNIEnv *env, jobject clazz, jstring filePath) {
+ ScopedUtfChars path(env, filePath);
+ if (path.c_str() == nullptr) {
+ return EINVAL;
+ }
+ ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ return enableFsverityForFd(env, clazz, rfd.get());
+}
+
// Returns whether the file has fs-verity enabled.
// 0 if it is not present, 1 if is present, and -errno if there was an error.
int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
@@ -126,6 +130,7 @@
}
const JNINativeMethod sMethods[] = {
{"enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity},
+ {"enableFsverityForFdNative", "(I)I", (void *)enableFsverityForFd},
{"statxForFsverityNative", "(Ljava/lang/String;)I", (void *)statxForFsverity},
{"measureFsverityNative", "(Ljava/lang/String;[B)I", (void *)measureFsverity},
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0378539..0b6b0a1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4542,6 +4542,15 @@
<permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a
+ {@link android.service.assist.classification.FieldClassificationService},
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_FIELD_CLASSIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a CredentialProviderService to ensure that only the
system can bind to it.
<p>Protection level: signature
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b2942df..8d644e9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -983,6 +983,13 @@
<integer-array name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis">
</integer-array>
+ <!-- Boolean indicating whether secondary built-in displays should have their orientation
+ match the active default display. This config assumes that the secondary display only
+ requires swapping ROTATION_90 and ROTATION_270.
+ TODO(b/265991392): This should eventually be configured and parsed in
+ display_settings.xml -->
+ <bool name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay">true</bool>
+
<!-- Indicate available ColorDisplayManager.COLOR_MODE_xxx. -->
<integer-array name="config_availableColorModes">
<!-- Example:
@@ -4166,12 +4173,19 @@
<bool name="config_quickSettingsSupported">true</bool>
<!-- The component name, flattened to a string, for the default autofill service
- to enabled for an user. This service must be trusted, as it can be activated
+ to enabled for a user. This service must be trusted, as it can be activated
without explicit consent of the user. If no autofill service with the
specified name exists on the device, autofill will be disabled by default.
-->
<string name="config_defaultAutofillService" translatable="false"></string>
+ <!-- The component name, flattened to a string, for the default field classification service
+ to enabled for a user. This service must be trusted, as it can be activated
+ without explicit consent of the user. If no field classification service with the
+ specified name exists on the device, field classification will be disabled by default.
+ -->
+ <string name="config_defaultFieldClassificationService" translatable="false"></string>
+
<!-- The package name for the OEM custom system textclassifier service.
This service must be trusted, as it can be activated without explicit consent of the user.
Example: "com.android.textclassifier"
@@ -6262,4 +6276,7 @@
"stopped" during initial boot of a device, or after an OTA update. Stopped state of
an app is not changed during subsequent reboots. -->
<bool name="config_stopSystemPackagesByDefault">false</bool>
+
+ <!-- Whether to show weather on the lock screen by default. -->
+ <bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dcdffe6..7754256 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3404,6 +3404,13 @@
TODO(b/265312193): Remove this workaround when this bug is fixed.-->
<java-symbol type="array" name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis" />
+ <!-- Boolean indicating whether secondary built-in displays should have their orientation
+ match the active default display. This config assumes that the secondary display only
+ requires swapping ROTATION_90 and ROTATION_270.
+ TODO(b/265991392): This should eventually be configured and parsed in
+ display_settings.xml -->
+ <java-symbol type="bool" name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay" />
+
<!-- Default user restrictions for the SYSTEM user -->
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -3712,6 +3719,7 @@
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
<java-symbol type="string" name="config_defaultAutofillService" />
+ <java-symbol type="string" name="config_defaultFieldClassificationService" />
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
<java-symbol type="string" name="config_defaultWellbeingPackage" />
@@ -4918,4 +4926,7 @@
<java-symbol type="array" name="config_displayShapeArray" />
<java-symbol type="bool" name="config_stopSystemPackagesByDefault"/>
+
+ <!-- Whether to show weather on the lockscreen by default. -->
+ <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
</resources>
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/core/tests/coretests/src/android/view/WindowLayoutTests.java
similarity index 89%
rename from services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
rename to core/tests/coretests/src/android/view/WindowLayoutTests.java
index 731a235..5cac98d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/core/tests/coretests/src/android/view/WindowLayoutTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.server.wm;
+package android.view;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
@@ -39,13 +37,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.view.DisplayCutout;
-import android.view.Gravity;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.WindowInsets;
-import android.view.WindowLayout;
-import android.view.WindowManager;
import android.window.ClientWindowFrames;
import androidx.test.filters.SmallTest;
@@ -72,6 +63,11 @@
new Rect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT);
private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0);
+ private static final int ID_STATUS_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, statusBars());
+ private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, navigationBars());
+
private final WindowLayout mWindowLayout = new WindowLayout();
private final ClientWindowFrames mFrames = new ClientWindowFrames();
@@ -90,9 +86,9 @@
mAttrs = new WindowManager.LayoutParams();
mState = new InsetsState();
mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
- mState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars()).setFrame(
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars()).setFrame(
0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
- mState.getOrCreateSource(ITYPE_NAVIGATION_BAR, navigationBars()).setFrame(
+ mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars()).setFrame(
0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
@@ -268,8 +264,8 @@
@Test
public void fitInvisibleInsets() {
- mState.setSourceVisible(ITYPE_STATUS_BAR, false);
- mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
+ mState.setSourceVisible(ID_STATUS_BAR, false);
+ mState.setSourceVisible(ID_NAVIGATION_BAR, false);
computeFrames();
assertInsetByTopBottom(0, 0, mFrames.displayFrame);
@@ -279,8 +275,8 @@
@Test
public void fitInvisibleInsetsIgnoringVisibility() {
- mState.setSourceVisible(ITYPE_STATUS_BAR, false);
- mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
+ mState.setSourceVisible(ID_STATUS_BAR, false);
+ mState.setSourceVisible(ID_NAVIGATION_BAR, false);
mAttrs.setFitInsetsIgnoringVisibility(true);
computeFrames();
@@ -324,11 +320,11 @@
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.frame);
}
@@ -365,17 +361,17 @@
@Test
public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
addDisplayCutout();
- mState.setSourceVisible(ITYPE_STATUS_BAR, false);
- mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
+ mState.setSourceVisible(ID_STATUS_BAR, false);
+ mState.setSourceVisible(ID_NAVIGATION_BAR, false);
mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.frame);
}
@@ -398,11 +394,11 @@
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.frame);
}
diff --git a/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS b/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS
new file mode 100644
index 0000000..2e96c97
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java b/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java
new file mode 100644
index 0000000..6b9d39c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.config.sysui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+public class SystemUiSystemPropertiesFlagsTest extends TestCase {
+
+ public class TestableDebugResolver extends SystemUiSystemPropertiesFlags.DebugResolver {
+ final Map<String, Boolean> mTestData = new HashMap<>();
+
+ @Override
+ public boolean getBoolean(String key, boolean defaultValue) {
+ Boolean testValue = mTestData.get(key);
+ return testValue == null ? defaultValue : testValue;
+ }
+
+ public void set(Flag flag, Boolean value) {
+ mTestData.put(flag.mSysPropKey, value);
+ }
+ }
+
+ private FlagResolver mProdResolver;
+ private TestableDebugResolver mDebugResolver;
+
+ private Flag mReleasedFlag;
+ private Flag mTeamfoodFlag;
+ private Flag mDevFlag;
+
+ public void setUp() {
+ mProdResolver = new SystemUiSystemPropertiesFlags.ProdResolver();
+ mDebugResolver = new TestableDebugResolver();
+ mReleasedFlag = SystemUiSystemPropertiesFlags.releasedFlag("mReleasedFlag");
+ mTeamfoodFlag = SystemUiSystemPropertiesFlags.teamfoodFlag("mTeamfoodFlag");
+ mDevFlag = SystemUiSystemPropertiesFlags.devFlag("mDevFlag");
+ }
+
+ public void tearDown() {
+ SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ }
+
+ public void testProdResolverReturnsDefault() {
+ assertThat(mProdResolver.isEnabled(mReleasedFlag)).isTrue();
+ assertThat(mProdResolver.isEnabled(mTeamfoodFlag)).isFalse();
+ assertThat(mProdResolver.isEnabled(mDevFlag)).isFalse();
+ }
+
+ public void testDebugResolverAndReleasedFlag() {
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
+
+ mDebugResolver.set(mReleasedFlag, false);
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isFalse();
+
+ mDebugResolver.set(mReleasedFlag, true);
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
+ }
+
+ private void assertTeamfoodFlag(Boolean flagValue, Boolean teamfood, boolean expected) {
+ mDebugResolver.set(mTeamfoodFlag, flagValue);
+ mDebugResolver.set(SystemUiSystemPropertiesFlags.TEAMFOOD, teamfood);
+ assertThat(mDebugResolver.isEnabled(mTeamfoodFlag)).isEqualTo(expected);
+ }
+
+ public void testDebugResolverAndTeamfoodFlag() {
+ assertTeamfoodFlag(null, null, false);
+ assertTeamfoodFlag(true, null, true);
+ assertTeamfoodFlag(false, null, false);
+ assertTeamfoodFlag(null, true, true);
+ assertTeamfoodFlag(true, true, true);
+ assertTeamfoodFlag(false, true, false);
+ assertTeamfoodFlag(null, false, false);
+ assertTeamfoodFlag(true, false, true);
+ assertTeamfoodFlag(false, false, false);
+ }
+
+ public void testDebugResolverAndDevFlag() {
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
+
+ mDebugResolver.set(mDevFlag, true);
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isTrue();
+
+ mDebugResolver.set(mDevFlag, false);
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bccf283..53f4747 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -61,6 +61,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/WindowToken.java"
},
+ "-2088209279": {
+ "message": "Notified TransitionController that the display is ready.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"-2072089308": {
"message": "Attempted to add window with token that is a sub-window: %s. Aborting.",
"level": "WARN",
@@ -1771,6 +1777,12 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-464564167": {
+ "message": "Current transition prevents automatic focus change",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-463348344": {
"message": "Removing and adding activity %s to root task at top callers=%s",
"level": "INFO",
@@ -2065,12 +2077,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/SurfaceAnimator.java"
},
- "-206549078": {
- "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: config_perDisplayFocusEnabled",
- "level": "INFO",
- "group": "WM_DEBUG_FOCUS_LIGHT",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-203358733": {
"message": "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
"level": "INFO",
@@ -2767,6 +2773,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "378890013": {
+ "message": "Apply and finish immediately because player is disabled for transition #%d .",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"385237117": {
"message": "moveFocusableActivityToTop: already on top and focused, activity=%s",
"level": "DEBUG",
@@ -3649,6 +3661,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1282992082": {
+ "message": "Disabling player for transition #%d because display isn't enabled yet",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1284122013": {
"message": "TaskFragment appeared name=%s",
"level": "VERBOSE",
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index fa173072..d39d4b4 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -412,11 +412,11 @@
private static ParsedRequiresPermission parseRequiresPermissionRecursively(
MethodInvocationTree tree, VisitorState state) {
- if (ENFORCE_VIA_CONTEXT.matches(tree, state)) {
+ if (ENFORCE_VIA_CONTEXT.matches(tree, state) && tree.getArguments().size() > 0) {
final ParsedRequiresPermission res = new ParsedRequiresPermission();
res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
return res;
- } else if (ENFORCE_VIA_CHECKER.matches(tree, state)) {
+ } else if (ENFORCE_VIA_CHECKER.matches(tree, state) && tree.getArguments().size() > 1) {
final ParsedRequiresPermission res = new ParsedRequiresPermission();
res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
return res;
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
index 388988e..38831b1 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -415,4 +415,27 @@
"}")
.doTest();
}
+
+ @Test
+ public void testInvalidFunctions() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/annotation/SuppressLint.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceLines("Example.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.annotation.SuppressLint;",
+ "import android.content.Context;",
+ "class Foo extends Context {",
+ " private static final String RED = \"red\";",
+ " public void checkPermission() {",
+ " }",
+ " @RequiresPermission(RED)",
+ " // BUG: Diagnostic contains:",
+ " public void exampleScoped(Context context) {",
+ " checkPermission();",
+ " }",
+ "}")
+ .doTest();
+ }
}
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 67963a3..d94e8e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -154,7 +154,7 @@
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setAdjacentTaskFragmentsWithRule(wct, launchingFragmentToken, secondaryFragmentToken, rule);
setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
false /* isStacked */);
}
@@ -167,7 +167,7 @@
void expandTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
- setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
+ clearAdjacentTaskFragments(wct, fragmentToken);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
@@ -238,26 +238,37 @@
wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
}
- void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
- if (secondary == null) {
- wct.clearAdjacentTaskFragments(primary);
- return;
- }
-
+ /**
+ * Sets the two given TaskFragments as adjacent to each other with respecting the given
+ * {@link SplitRule} for {@link WindowContainerTransaction.TaskFragmentAdjacentParams}.
+ */
+ void setAdjacentTaskFragmentsWithRule(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
final boolean finishSecondaryWithPrimary =
- splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
+ SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
final boolean finishPrimaryWithSecondary =
- splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
+ SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
}
+ setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
+ }
+
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
+ void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ // Clear primary will also clear secondary.
+ wct.clearAdjacentTaskFragments(fragmentToken);
+ }
+
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
boolean isStacked) {
@@ -268,7 +279,7 @@
} else {
finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
}
- wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+ setCompanionTaskFragment(wct, primary, finishPrimaryWithSecondary ? secondary : null);
final boolean finishSecondaryWithPrimary;
if (isStacked) {
@@ -277,7 +288,12 @@
} else {
finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
}
- wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+ setCompanionTaskFragment(wct, secondary, finishSecondaryWithPrimary ? primary : null);
+ }
+
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
+ @Nullable IBinder secondary) {
+ wct.setCompanionTaskFragment(primary, secondary);
}
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
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 4ed53a8..2c1ddf7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -525,6 +525,9 @@
// All overrides will be cleanup.
container.setLastRequestedBounds(null /* bounds */);
container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
+ container.clearLastAdjacentTaskFragment();
+ container.setLastCompanionTaskFragment(null /* fragmentToken */);
+ container.setLastRequestAnimationParams(TaskFragmentAnimationParams.DEFAULT);
cleanupForEnterPip(wct, container);
} else if (wasInPip) {
// Exit PIP.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 5d51760..0408511 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -385,10 +385,9 @@
// secondaryContainer could not be finished.
boolean isStacked = !shouldShowSplit(splitAttributes);
if (isStacked) {
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
- null /* secondary */, null /* splitRule */);
+ clearAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken());
} else {
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ setAdjacentTaskFragmentsWithRule(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
@@ -425,7 +424,7 @@
fragmentOptions.getFragmentToken());
if (container == null) {
throw new IllegalStateException(
- "Creating a task fragment that is not registered with controller.");
+ "Creating a TaskFragment that is not registered with controller.");
}
container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
@@ -439,7 +438,7 @@
TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
throw new IllegalStateException(
- "Resizing a task fragment that is not registered with controller.");
+ "Resizing a TaskFragment that is not registered with controller.");
}
if (container.areLastRequestedBoundsEqual(relBounds)) {
@@ -456,7 +455,7 @@
@NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
- throw new IllegalStateException("Setting windowing mode for a task fragment that is"
+ throw new IllegalStateException("Setting windowing mode for a TaskFragment that is"
+ " not registered with controller.");
}
@@ -474,7 +473,7 @@
@NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
- throw new IllegalStateException("Setting animation params for a task fragment that is"
+ throw new IllegalStateException("Setting animation params for a TaskFragment that is"
+ " not registered with controller.");
}
@@ -487,6 +486,64 @@
super.updateAnimationParams(wct, fragmentToken, animationParams);
}
+ @Override
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
+ final TaskFragmentContainer primaryContainer = mController.getContainer(primary);
+ final TaskFragmentContainer secondaryContainer = mController.getContainer(secondary);
+ if (primaryContainer == null || secondaryContainer == null) {
+ throw new IllegalStateException("setAdjacentTaskFragments on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (primaryContainer.isLastAdjacentTaskFragmentEqual(secondary, adjacentParams)
+ && secondaryContainer.isLastAdjacentTaskFragmentEqual(primary, adjacentParams)) {
+ // Return early if the same adjacent TaskFragments were already requested
+ return;
+ }
+
+ primaryContainer.setLastAdjacentTaskFragment(secondary, adjacentParams);
+ secondaryContainer.setLastAdjacentTaskFragment(primary, adjacentParams);
+ super.setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
+ }
+
+ @Override
+ void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException("clearAdjacentTaskFragments on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastAdjacentTaskFragmentEqual(null /* fragmentToken*/, null /* params */)) {
+ // Return early if no adjacent TaskFragment was yet requested
+ return;
+ }
+
+ container.clearLastAdjacentTaskFragment();
+ super.clearAdjacentTaskFragments(wct, fragmentToken);
+ }
+
+ @Override
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
+ @Nullable IBinder secondary) {
+ final TaskFragmentContainer container = mController.getContainer(primary);
+ if (container == null) {
+ throw new IllegalStateException("setCompanionTaskFragment on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastCompanionTaskFragmentEqual(secondary)) {
+ // Return early if the same companion TaskFragment was already requested
+ return;
+ }
+
+ container.setLastCompanionTaskFragment(secondary);
+ super.setCompanionTaskFragment(wct, primary, secondary);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
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 ccb274a..b38f824 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -40,6 +40,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
/**
* Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
@@ -117,6 +118,27 @@
private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
/**
+ * TaskFragment token that was requested last via
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
+ */
+ @Nullable
+ private IBinder mLastAdjacentTaskFragment;
+
+ /**
+ * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last
+ * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
+ */
+ @Nullable
+ private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams;
+
+ /**
+ * TaskFragment token that was requested last via
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}.
+ */
+ @Nullable
+ private IBinder mLastCompanionTaskFragment;
+
+ /**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@@ -591,6 +613,7 @@
/**
* Checks if last requested bounds are equal to the provided value.
* The requested bounds are relative bounds in parent coordinate.
+ * @see WindowContainerTransaction#setRelativeBounds
*/
boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
return (relBounds == null && mLastRequestedBounds.isEmpty())
@@ -600,6 +623,7 @@
/**
* Updates the last requested bounds.
* The requested bounds are relative bounds in parent coordinate.
+ * @see WindowContainerTransaction#setRelativeBounds
*/
void setLastRequestedBounds(@Nullable Rect relBounds) {
if (relBounds == null) {
@@ -609,13 +633,9 @@
}
}
- @NonNull
- Rect getLastRequestedBounds() {
- return mLastRequestedBounds;
- }
-
/**
* Checks if last requested windowing mode is equal to the provided value.
+ * @see WindowContainerTransaction#setWindowingMode
*/
boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) {
return mLastRequestedWindowingMode == windowingMode;
@@ -623,6 +643,7 @@
/**
* Updates the last requested windowing mode.
+ * @see WindowContainerTransaction#setWindowingMode
*/
void setLastRequestedWindowingMode(@WindowingMode int windowingModes) {
mLastRequestedWindowingMode = windowingModes;
@@ -630,6 +651,7 @@
/**
* Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
boolean areLastRequestedAnimationParamsEqual(
@NonNull TaskFragmentAnimationParams animationParams) {
@@ -638,11 +660,66 @@
/**
* Updates the last requested {@link TaskFragmentAnimationParams}.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
mLastAnimationParams = animationParams;
}
+ /**
+ * Checks if last requested adjacent TaskFragment token and params are equal to the provided
+ * values.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
+ * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
+ */
+ boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) {
+ return Objects.equals(mLastAdjacentTaskFragment, fragmentToken)
+ && Objects.equals(mLastAdjacentParams, params);
+ }
+
+ /**
+ * Updates the last requested adjacent TaskFragment token and params.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
+ */
+ void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken,
+ @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) {
+ mLastAdjacentTaskFragment = fragmentToken;
+ mLastAdjacentParams = params;
+ }
+
+ /**
+ * Clears the last requested adjacent TaskFragment token and params.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
+ */
+ void clearLastAdjacentTaskFragment() {
+ final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null
+ ? mController.getContainer(mLastAdjacentTaskFragment)
+ : null;
+ mLastAdjacentTaskFragment = null;
+ mLastAdjacentParams = null;
+ if (lastAdjacentTaskFragment != null) {
+ // Clear the previous adjacent TaskFragment as well.
+ lastAdjacentTaskFragment.clearLastAdjacentTaskFragment();
+ }
+ }
+
+ /**
+ * Checks if last requested companion TaskFragment token is equal to the provided value.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
+ */
+ boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) {
+ return Objects.equals(mLastCompanionTaskFragment, fragmentToken);
+ }
+
+ /**
+ * Updates the last requested companion TaskFragment token.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
+ */
+ void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) {
+ mLastCompanionTaskFragment = fragmentToken;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index be7c26e..8330156 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -177,6 +177,64 @@
}
@Test
+ public void testSetAdjacentTaskFragments() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+ verify(mTransaction).setAdjacentTaskFragments(container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+
+ verify(mTransaction, never()).setAdjacentTaskFragments(any(), any(), any());
+ }
+
+ @Test
+ public void testClearAdjacentTaskFragments() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ // No request to clear as it is not set by default.
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ verify(mTransaction, never()).clearAdjacentTaskFragments(any());
+
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ verify(mTransaction).clearAdjacentTaskFragments(container0.getTaskFragmentToken());
+
+ // No request to clear on either of the previous cleared TasKFragments.
+ clearInvocations(mTransaction);
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container1.getTaskFragmentToken());
+
+ verify(mTransaction, never()).clearAdjacentTaskFragments(any());
+ }
+
+ @Test
+ public void testSetCompanionTaskFragment() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+ verify(mTransaction).setCompanionTaskFragment(container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+
+ verify(mTransaction, never()).setCompanionTaskFragment(any(), any());
+ }
+
+ @Test
public void testUpdateAnimationParams() {
final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index a835167..24cfc9d 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -38,6 +38,8 @@
: mContext(context) {
std::scoped_lock lock(mLock);
+ mLocked.stylusHoverMode = false;
+
mLocked.animationFrameIndex = 0;
mLocked.lastFrameUpdatedTime = 0;
@@ -47,7 +49,8 @@
mLocked.pointerAlpha = 0.0f; // pointer is initially faded
mLocked.pointerSprite = mContext.getSpriteController()->createSprite();
mLocked.updatePointerIcon = false;
- mLocked.requestedPointerType = mContext.getPolicy()->getDefaultPointerIconId();
+ mLocked.requestedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
+ mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
mLocked.resourcesLoaded = false;
@@ -184,6 +187,15 @@
}
}
+void MouseCursorController::setStylusHoverMode(bool stylusHoverMode) {
+ std::scoped_lock lock(mLock);
+
+ if (mLocked.stylusHoverMode != stylusHoverMode) {
+ mLocked.stylusHoverMode = stylusHoverMode;
+ mLocked.updatePointerIcon = true;
+ }
+}
+
void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
std::scoped_lock lock(mLock);
@@ -339,7 +351,7 @@
bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
std::map<PointerIconStyle, PointerAnimation>::const_iterator iter =
- mLocked.animationResources.find(mLocked.requestedPointerType);
+ mLocked.animationResources.find(mLocked.resolvedPointerType);
if (iter == mLocked.animationResources.end()) {
return false;
}
@@ -381,14 +393,23 @@
}
if (mLocked.updatePointerIcon) {
- if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) {
+ mLocked.resolvedPointerType = mLocked.requestedPointerType;
+ const PointerIconStyle defaultPointerIconId =
+ mContext.getPolicy()->getDefaultPointerIconId();
+ if (mLocked.resolvedPointerType == PointerIconStyle::TYPE_NOT_SPECIFIED) {
+ mLocked.resolvedPointerType = mLocked.stylusHoverMode
+ ? mContext.getPolicy()->getDefaultStylusIconId()
+ : defaultPointerIconId;
+ }
+
+ if (mLocked.resolvedPointerType == defaultPointerIconId) {
mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
} else {
std::map<PointerIconStyle, SpriteIcon>::const_iterator iter =
- mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
+ mLocked.additionalMouseResources.find(mLocked.resolvedPointerType);
if (iter != mLocked.additionalMouseResources.end()) {
std::map<PointerIconStyle, PointerAnimation>::const_iterator anim_iter =
- mLocked.animationResources.find(mLocked.requestedPointerType);
+ mLocked.animationResources.find(mLocked.resolvedPointerType);
if (anim_iter != mLocked.animationResources.end()) {
mLocked.animationFrameIndex = 0;
mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -396,7 +417,7 @@
}
mLocked.pointerSprite->setIcon(iter->second);
} else {
- ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
+ ALOGW("Can't find the resource for icon id %d", mLocked.resolvedPointerType);
mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
}
}
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 208d33d..db0ab56 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -53,6 +53,7 @@
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
+ void setStylusHoverMode(bool stylusHoverMode);
void updatePointerIcon(PointerIconStyle iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
@@ -74,6 +75,7 @@
struct Locked {
DisplayViewport viewport;
+ bool stylusHoverMode;
size_t animationFrameIndex;
nsecs_t lastFrameUpdatedTime;
@@ -92,6 +94,7 @@
std::map<PointerIconStyle, PointerAnimation> animationResources;
PointerIconStyle requestedPointerType;
+ PointerIconStyle resolvedPointerType;
int32_t buttonState;
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 099efd3..fedf58d 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -195,7 +195,11 @@
return;
}
- if (presentation == Presentation::POINTER) {
+ if (presentation == Presentation::POINTER || presentation == Presentation::STYLUS_HOVER) {
+ // For now, we support stylus hover using the mouse cursor implementation.
+ // TODO: Add proper support for stylus hover icons.
+ mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+
mCursorController.getAdditionalMouseResources();
clearSpotsLocked();
}
@@ -249,7 +253,8 @@
if (mCursorController.resourcesLoaded()) {
bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER) {
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
getAdditionalMouseResources = true;
}
mCursorController.reloadPointerResources(getAdditionalMouseResources);
@@ -260,7 +265,8 @@
std::scoped_lock lock(getLock());
bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER) {
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
getAdditionalMouseResources = true;
}
mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 1797428..96d83a5 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -79,6 +79,7 @@
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
int32_t displayId) = 0;
virtual PointerIconStyle getDefaultPointerIconId() = 0;
+ virtual PointerIconStyle getDefaultStylusIconId() = 0;
virtual PointerIconStyle getCustomPointerIconId() = 0;
virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
};
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index a6a4115..c820d00 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -35,6 +35,7 @@
CURSOR_TYPE_ANCHOR,
CURSOR_TYPE_ADDITIONAL,
CURSOR_TYPE_ADDITIONAL_ANIM,
+ CURSOR_TYPE_STYLUS,
CURSOR_TYPE_CUSTOM = -1,
};
@@ -57,6 +58,7 @@
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
int32_t displayId) override;
virtual PointerIconStyle getDefaultPointerIconId() override;
+ virtual PointerIconStyle getDefaultStylusIconId() override;
virtual PointerIconStyle getCustomPointerIconId() override;
virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
@@ -105,6 +107,11 @@
(*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
(*outAnimationResources)[static_cast<PointerIconStyle>(cursorType)] = anim;
+ // CURSOR_TYPE_STYLUS doesn't have animation resource.
+ cursorType = CURSOR_TYPE_STYLUS;
+ loadPointerIconForType(&icon, cursorType);
+ (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
+
additionalMouseResourcesLoaded = true;
}
@@ -112,6 +119,10 @@
return static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT);
}
+PointerIconStyle MockPointerControllerPolicyInterface::getDefaultStylusIconId() {
+ return static_cast<PointerIconStyle>(CURSOR_TYPE_STYLUS);
+}
+
PointerIconStyle MockPointerControllerPolicyInterface::getCustomPointerIconId() {
return static_cast<PointerIconStyle>(CURSOR_TYPE_CUSTOM);
}
@@ -214,6 +225,21 @@
mPointerController->reloadPointerResources();
}
+TEST_F(PointerControllerTest, useStylusTypeForStylusHover) {
+ ensureDisplayViewportIsSet();
+ mPointerController->setPresentation(PointerController::Presentation::STYLUS_HOVER);
+ mPointerController->unfade(PointerController::Transition::IMMEDIATE);
+ std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_STYLUS);
+ EXPECT_CALL(*mPointerSprite, setVisible(true));
+ EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+ EXPECT_CALL(*mPointerSprite,
+ setIcon(AllOf(Field(&SpriteIcon::style,
+ static_cast<PointerIconStyle>(CURSOR_TYPE_STYLUS)),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ mPointerController->reloadPointerResources();
+}
+
TEST_F(PointerControllerTest, updatePointerIcon) {
ensureDisplayViewportIsSet();
mPointerController->setPresentation(PointerController::Presentation::POINTER);
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index a26249a..ab78536 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -259,7 +259,7 @@
@IntDef(
flag = true,
prefix = {"FLAG_"},
- value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE})
+ value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE, FLAG_ONGOING_SESSION_MANAGED})
public @interface Flags {}
/**
@@ -269,6 +269,22 @@
public static final int FLAG_ONGOING_SESSION = 1;
/**
+ * Signals that the ongoing session on the corresponding route is managed by the current
+ * user of the app.
+ *
+ * <p>The system can use this flag to provide visual indication that the route is not only
+ * hosting a session, but also that the user has ownership over said session.
+ *
+ * <p>This flag is ignored if {@link #FLAG_ONGOING_SESSION} is not set, or if the
+ * corresponding route is not currently selected.
+ *
+ * <p>This flag does not affect volume adjustment (see {@link VolumeProvider}, and {@link
+ * MediaRoute2Info#getVolumeHandling()}), or any aspect other than the visual representation
+ * of the corresponding item.
+ */
+ public static final int FLAG_ONGOING_SESSION_MANAGED = 1 << 1;
+
+ /**
* The corresponding route is specially likely to be selected by the user.
*
* <p>A UI reflecting this preference may reserve a specific space for suggested routes,
@@ -276,7 +292,7 @@
* number supported by the UI, the routes listed first in {@link
* RouteListingPreference#getItems()} will take priority.
*/
- public static final int FLAG_SUGGESTED_ROUTE = 1 << 1;
+ public static final int FLAG_SUGGESTED_ROUTE = 1 << 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -345,6 +361,7 @@
mFlags = builder.mFlags;
mSubText = builder.mSubText;
mCustomSubtextMessage = builder.mCustomSubtextMessage;
+ validateCustomMessageSubtext();
}
private Item(Parcel in) {
@@ -354,6 +371,7 @@
mFlags = in.readInt();
mSubText = in.readInt();
mCustomSubtextMessage = in.readCharSequence();
+ validateCustomMessageSubtext();
}
/**
@@ -467,6 +485,16 @@
mRouteId, mSelectionBehavior, mFlags, mSubText, mCustomSubtextMessage);
}
+ // Internal methods.
+
+ private void validateCustomMessageSubtext() {
+ Preconditions.checkArgument(
+ mSubText != SUBTEXT_CUSTOM || mCustomSubtextMessage != null,
+ "The custom subtext message cannot be null if subtext is SUBTEXT_CUSTOM.");
+ }
+
+ // Internal classes.
+
/** Builder for {@link Item}. */
public static final class Builder {
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index b1356f5..cdeef2b 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -575,7 +575,7 @@
Log.w(TAG, "onError - session not created");
return;
}
- if (mCallback == null) {
+ if (mCallback != null) {
mCallback.onError(error);
}
if (mTvIAppView != null) {
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index ab2d815..6c463e1 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -22,7 +22,7 @@
android:orientation="horizontal"
android:paddingStart="32dp"
android:paddingEnd="32dp"
- android:paddingBottom="14dp">
+ android:paddingTop="12dp">
<ImageView
android:id="@+id/permission_icon"
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 7397688..b842761 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
<string name="app_label">Companion Device Manager</string>
<!-- Title of the device association confirmation dialog. -->
- <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access your <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
+ <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
<!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
@@ -31,10 +31,10 @@
<string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by <strong><xliff:g id="app_name" example="Android Wear">%2$s</xliff:g></strong></string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+ <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+ <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string>
<!-- TODO(b/256140614) To replace all glasses related strings with final versions -->
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -43,10 +43,10 @@
<string name="profile_name_glasses">glasses</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+ <string name="summary_glasses">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+ <string name="summary_glasses_single_device">The app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -99,7 +99,10 @@
<string name="profile_name_generic">device</string>
<!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_generic"></string>
+ <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>.</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device.</string>
<!-- ================= Buttons ================= -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index c5ed5c9..918f9c6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -546,17 +546,16 @@
}
if (deviceProfile == null) {
- // Summary is not needed for null profile.
- mSummary.setVisibility(View.GONE);
+ summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
mConstraintList.setVisibility(View.GONE);
} else {
+ summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile),
+ getString(PROFILES_NAME.get(deviceProfile)), appLabel);
mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
setupPermissionList();
}
title = getHtmlFromResources(this, TITLES.get(deviceProfile), appLabel, deviceName);
- summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile),
- getString(PROFILES_NAME.get(deviceProfile)), appLabel);
profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
mTitle.setText(title);
@@ -586,7 +585,6 @@
if (deviceProfile == null) {
summary = getHtmlFromResources(this, summaryResourceId);
- mSummary.setVisibility(View.GONE);
} else {
summary = getHtmlFromResources(this, summaryResourceId, profileName, appLabel);
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 6f5f4fe..e3fd354 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -88,7 +88,7 @@
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch_single_device);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_single_device);
- map.put(null, R.string.summary_generic);
+ map.put(null, R.string.summary_generic_single_device);
SUMMARIES = unmodifiableMap(map);
}
diff --git a/packages/SettingsLib/ActivityEmbedding/OWNERS b/packages/SettingsLib/ActivityEmbedding/OWNERS
new file mode 100644
index 0000000..7022402
--- /dev/null
+++ b/packages/SettingsLib/ActivityEmbedding/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+arcwang@google.com
+chiujason@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
index 1407725..f89be9f 100644
--- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
+++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
@@ -23,7 +23,7 @@
import android.util.Log;
import androidx.core.os.BuildCompat;
-import androidx.window.embedding.SplitController;
+import androidx.window.embedding.ActivityEmbeddingController;
import com.android.settingslib.utils.BuildCompatUtils;
@@ -84,7 +84,7 @@
* @param activity Activity that needs the check
*/
public static boolean isActivityEmbedded(Activity activity) {
- return SplitController.getInstance().isActivityEmbedded(activity);
+ return ActivityEmbeddingController.getInstance(activity).isActivityEmbedded(activity);
}
/**
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 36315b5..1b3020a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -1,12 +1,11 @@
# People who can approve changes for submission
dsandler@android.com
edgarwang@google.com
-emilychuang@google.com
evanlaird@google.com
-hanxu@google.com
juliacr@google.com
+lijun@google.com
+songchenxi@google.com
yantingyang@google.com
-ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
index eeab085..69c4705 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -46,7 +46,7 @@
object ChartPageProvider : SettingsPageProvider {
override val name = "Chart"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun getTitle(arguments: Bundle?): String {
return TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 2328fcb..9c7e0ce 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -37,7 +37,7 @@
object FooterPageProvider : SettingsPageProvider {
override val name = "Footer"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index 45b7989..ee22b96 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -36,7 +36,7 @@
object IllustrationPageProvider : SettingsPageProvider {
override val name = "Illustration"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 74e49a6..1051549 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -42,7 +42,7 @@
object SliderPageProvider : SettingsPageProvider {
override val name = "Slider"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index d100d9d..442ace8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -38,7 +38,7 @@
object MainSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "MainSwitchPreference"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 6ad4bd8..b67e066 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -43,7 +43,7 @@
object SwitchPreferencePageProvider : SettingsPageProvider {
override val name = "SwitchPreference"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index 770f9a0..a2cd283 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -40,7 +40,7 @@
object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "TwoTargetSwitchPreference"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 838c0cf..494e3cc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -161,7 +161,7 @@
.add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
.add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
.add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
- .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
+ .add(ColumnEnum.PAGE_BROWSABLE.id, if (page.isBrowsable()) 1 else 0)
}
return cursor
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
index bb9a134..fc6160e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -28,7 +28,7 @@
PAGE_ROUTE("pageRoute"),
PAGE_INTENT_URI("pageIntent"),
PAGE_ENTRY_COUNT("entryCount"),
- HAS_RUNTIME_PARAM("hasRuntimeParam"),
+ PAGE_BROWSABLE("pageBrowsable"),
PAGE_START_ADB("pageStartAdb"),
// Columns related to entry
@@ -67,7 +67,7 @@
ColumnEnum.PAGE_ROUTE,
ColumnEnum.PAGE_INTENT_URI,
ColumnEnum.PAGE_ENTRY_COUNT,
- ColumnEnum.HAS_RUNTIME_PARAM,
+ ColumnEnum.PAGE_BROWSABLE,
)
),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index bccd8aa..f1b1abd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -39,7 +39,7 @@
import androidx.navigation.NavGraph.Companion.findStartDestination
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.NullPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@@ -106,14 +106,13 @@
@Composable
private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
- val nullPage = SettingsPage.createNull()
AnimatedNavHost(
navController = navController,
- startDestination = nullPage.sppName,
+ startDestination = NullPageProvider.name,
) {
val slideEffect = tween<IntOffset>(durationMillis = 300)
val fadeEffect = tween<Float>(durationMillis = 300)
- composable(nullPage.sppName) {}
+ composable(NullPageProvider.name) {}
for (spp in allProvider) {
composable(
route = spp.name + spp.parameter.navRoute(),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 44714ab..4b73e94 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -24,6 +24,7 @@
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spa.framework.util.genEntryId
private const val INJECT_ENTRY_NAME = "INJECT"
private const val ROOT_ENTRY_NAME = "ROOT"
@@ -188,7 +189,7 @@
val page = fromPage ?: owner
val isEnabled = page.isEnabled()
return SettingsEntry(
- id = id(),
+ id = genEntryId(name, owner, fromPage, toPage),
name = name,
owner = owner,
displayName = displayName,
@@ -274,11 +275,6 @@
return this
}
- // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
- private fun id(): String {
- return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
- }
-
companion object {
fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
return SettingsEntryBuilder(entryName, owner)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 14b1629..14dc785 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -49,7 +49,7 @@
entryMap = mutableMapOf()
pageWithEntryMap = mutableMapOf()
- val nullPage = SettingsPage.createNull()
+ val nullPage = NullPageProvider.createSettingsPage()
val entryQueue = LinkedList<SettingsEntry>()
for (page in sppRepository.getAllRootPages()) {
val rootEntry =
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 94cfcc2..c810648 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -19,12 +19,9 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.framework.util.isRuntimeParam
import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.framework.util.normalize
-import com.android.settingslib.spa.framework.util.normalizeArgList
-
-private const val NULL_PAGE_NAME = "NULL"
/**
* Defines data to identify a Settings page.
@@ -46,10 +43,7 @@
val arguments: Bundle? = null,
) {
companion object {
- fun createNull(): SettingsPage {
- return create(NULL_PAGE_NAME)
- }
-
+ // TODO: cleanup it once all its usage in Settings are switched to Spp.createSettingsPage
fun create(
name: String,
displayName: String? = null,
@@ -57,23 +51,13 @@
arguments: Bundle? = null
): SettingsPage {
return SettingsPage(
- id = id(name, parameter, arguments),
+ id = genPageId(name, parameter, arguments),
sppName = name,
displayName = displayName ?: name,
parameter = parameter,
arguments = arguments
)
}
-
- // The unique id of this page, which is computed by name + normalized(arguments)
- private fun id(
- name: String,
- parameter: List<NamedNavArgument> = emptyList(),
- arguments: Bundle? = null
- ): String {
- val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
- return "$name:${normArguments?.toString()}".toHashId()
- }
}
// Returns if this Settings Page is created by the given Spp.
@@ -85,43 +69,20 @@
return sppName + parameter.navLink(arguments)
}
- fun hasRuntimeParam(): Boolean {
- for (navArg in parameter) {
- if (navArg.isRuntimeParam()) return true
- }
- return false
- }
-
fun isBrowsable(): Boolean {
- return !isCreateBy(NULL_PAGE_NAME) && !hasRuntimeParam()
- }
-
- private fun getProvider(): SettingsPageProvider? {
- if (!SpaEnvironmentFactory.isReady()) return null
- val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
- return pageProviderRepository.getProviderOrNull(sppName)
+ if (sppName == NullPageProvider.name) return false
+ for (navArg in parameter) {
+ if (navArg.isRuntimeParam()) return false
+ }
+ return true
}
fun isEnabled(): Boolean {
- return getProvider()?.isEnabled(arguments) ?: false
+ return getPageProvider(sppName)?.isEnabled(arguments) ?: false
}
@Composable
fun UiLayout() {
- getProvider()?.Page(arguments)
+ getPageProvider(sppName)?.Page(arguments)
}
}
-
-fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
- return SettingsPage.create(
- name = name,
- displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true)
- .joinToString("") { arg -> "/$arg" },
- parameter = parameter,
- arguments = arguments,
- )
-}
-
-fun String.toHashId(): String {
- return this.hashCode().toUInt().toString(36)
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index c564130..18f964e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -20,8 +20,12 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.genPageId
+import com.android.settingslib.spa.framework.util.normalizeArgList
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+private const val NULL_PAGE_NAME = "NULL"
+
/**
* An SettingsPageProvider which is used to create Settings page instances.
*/
@@ -62,3 +66,24 @@
}
}
}
+
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+ return SettingsPage(
+ id = genPageId(name, parameter, arguments),
+ sppName = name,
+ displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true)
+ .joinToString("") { arg -> "/$arg" },
+ parameter = parameter,
+ arguments = arguments,
+ )
+}
+
+object NullPageProvider : SettingsPageProvider {
+ override val name = NULL_PAGE_NAME
+}
+
+fun getPageProvider(sppName: String): SettingsPageProvider? {
+ if (!SpaEnvironmentFactory.isReady()) return null
+ val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+ return pageProviderRepository.getProviderOrNull(sppName)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/UniqueId.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/UniqueId.kt
new file mode 100644
index 0000000..3b0ff7d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/UniqueId.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+
+fun genPageId(
+ sppName: String,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+): String {
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
+ return "$sppName:${normArguments?.toString()}".toHashId()
+}
+
+fun genEntryId(
+ name: String,
+ owner: SettingsPage,
+ fromPage: SettingsPage? = null,
+ toPage: SettingsPage? = null
+): String {
+ return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
+}
+
+// TODO: implement a better hash function
+private fun String.toHashId(): String {
+ return this.hashCode().toUInt().toString(36)
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index c0b7464..730aa8f 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -19,12 +19,12 @@
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.genEntryId
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer1
import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
-import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,18 +41,18 @@
val pageWithEntry = entryRepository.getAllPageWithEntry()
assertThat(pageWithEntry.size).isEqualTo(3)
assertThat(
- entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+ entryRepository.getPageWithEntry(genPageId("SppHome"))
?.entries?.size
).isEqualTo(1)
assertThat(
- entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+ entryRepository.getPageWithEntry(genPageId("SppLayer1"))
?.entries?.size
).isEqualTo(3)
assertThat(
- entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+ entryRepository.getPageWithEntry(genPageId("SppLayer2"))
?.entries?.size
).isEqualTo(2)
- assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+ assertThat(entryRepository.getPageWithEntry(genPageId("SppWithParam"))).isNull()
}
@Test
@@ -61,17 +61,17 @@
assertThat(entry.size).isEqualTo(7)
assertThat(
entryRepository.getEntry(
- getUniqueEntryId(
+ genEntryId(
"ROOT",
SppHome.createSettingsPage(),
- SettingsPage.createNull(),
+ NullPageProvider.createSettingsPage(),
SppHome.createSettingsPage(),
)
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
SppHome.createSettingsPage(),
@@ -81,7 +81,7 @@
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer2.createSettingsPage(),
SppLayer1.createSettingsPage(),
@@ -91,22 +91,22 @@
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+ genEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+ genEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+ genEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
)
).isNotNull()
}
@@ -115,21 +115,21 @@
fun testGetEntryPath() {
assertThat(
entryRepository.getEntryPathWithDisplayName(
- getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
.inOrder()
assertThat(
entryRepository.getEntryPathWithTitle(
- getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+ genEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
"entryTitle"
)
).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
assertThat(
entryRepository.getEntryPathWithDisplayName(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
SppHome.createSettingsPage(),
@@ -140,7 +140,7 @@
assertThat(
entryRepository.getEntryPathWithTitle(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer2.createSettingsPage(),
SppLayer1.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 6de1ae5..5754c9b 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -23,11 +23,12 @@
import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.genEntryId
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.slice.appendSpaParams
import com.android.settingslib.spa.slice.getEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
-import com.android.settingslib.spa.tests.testutils.getUniquePageId
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -64,9 +65,9 @@
@Test
fun testBuildBasic() {
- val owner = SettingsPage.create("mySpp")
+ val owner = createSettingsPage("mySpp")
val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
- assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntry")
assertThat(entry.owner.sppName).isEqualTo("mySpp")
assertThat(entry.owner.displayName).isEqualTo("mySpp")
@@ -80,19 +81,19 @@
@Test
fun testBuildWithLink() {
- val owner = SettingsPage.create("mySpp")
- val fromPage = SettingsPage.create("fromSpp")
- val toPage = SettingsPage.create("toSpp")
+ val owner = createSettingsPage("mySpp")
+ val fromPage = createSettingsPage("fromSpp")
+ val toPage = createSettingsPage("toSpp")
val entryFrom =
SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
- assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+ assertThat(entryFrom.id).isEqualTo(genEntryId("myEntry", owner, owner, toPage))
assertThat(entryFrom.displayName).isEqualTo("myEntry")
assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
val entryTo =
SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
- assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+ assertThat(entryTo.id).isEqualTo(genEntryId("myEntry", owner, fromPage, owner))
assertThat(entryTo.displayName).isEqualTo("myEntry")
assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
@@ -100,10 +101,10 @@
@Test
fun testBuildInject() {
- val owner = SettingsPage.create("mySpp")
+ val owner = createSettingsPage("mySpp")
val entryInject = SettingsEntryBuilder.createInject(owner).build()
assertThat(entryInject.id).isEqualTo(
- getUniqueEntryId(
+ genEntryId(
INJECT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
@@ -114,10 +115,10 @@
@Test
fun testBuildRoot() {
- val owner = SettingsPage.create("mySpp")
+ val owner = createSettingsPage("mySpp")
val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
assertThat(entryInject.id).isEqualTo(
- getUniqueEntryId(
+ genEntryId(
ROOT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
@@ -129,7 +130,7 @@
@Test
fun testSetAttributes() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val owner = SettingsPage.create("SppHome")
+ val owner = createSettingsPage("SppHome")
val entryBuilder =
SettingsEntryBuilder.create(owner, "myEntry")
.setDisplayName("myEntryDisplay")
@@ -138,7 +139,7 @@
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entry = entryBuilder.build()
- assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntryDisplay")
assertThat(entry.fromPage).isNull()
assertThat(entry.toPage).isNull()
@@ -148,7 +149,7 @@
assertThat(entry.hasSliceSupport).isTrue()
// Test disabled Spp
- val ownerDisabled = SettingsPage.create("SppDisabled")
+ val ownerDisabled = createSettingsPage("SppDisabled")
val entryBuilderDisabled =
SettingsEntryBuilder.create(ownerDisabled, "myEntry")
.setDisplayName("myEntryDisplay")
@@ -157,7 +158,7 @@
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entryDisabled = entryBuilderDisabled.build()
- assertThat(entryDisabled.id).isEqualTo(getUniqueEntryId("myEntry", ownerDisabled))
+ assertThat(entryDisabled.id).isEqualTo(genEntryId("myEntry", ownerDisabled))
assertThat(entryDisabled.displayName).isEqualTo("myEntryDisplay")
assertThat(entryDisabled.fromPage).isNull()
assertThat(entryDisabled.toPage).isNull()
@@ -173,7 +174,7 @@
// Clear SppHome in spa environment
SpaEnvironmentFactory.reset()
val entry3 = entryBuilder.build()
- assertThat(entry3.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry3.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry3.displayName).isEqualTo("myEntryDisplay")
assertThat(entry3.fromPage).isNull()
assertThat(entry3.toPage).isNull()
@@ -186,12 +187,12 @@
@Test
fun testSetMarco() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val owner = SettingsPage.create("SppHome", arguments = bundleOf("param" to "v1"))
+ val owner = createSettingsPage("SppHome", arguments = bundleOf("param" to "v1"))
val entry = SettingsEntryBuilder.create(owner, "myEntry").setMacro {
assertThat(it?.getString("param")).isEqualTo("v1")
assertThat(it?.getString("rtParam")).isEqualTo("v2")
assertThat(it?.getString("unknown")).isNull()
- MacroForTest(getUniquePageId("SppHome"), getUniqueEntryId("myEntry", owner))
+ MacroForTest(genPageId("SppHome"), genEntryId("myEntry", owner))
}.build()
val rtArguments = bundleOf("rtParam" to "v2")
@@ -211,8 +212,8 @@
@Test
fun testSetSliceDataFn() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val owner = SettingsPage.create("SppHome")
- val entryId = getUniqueEntryId("myEntry", owner)
+ val owner = createSettingsPage("SppHome")
+ val entryId = genEntryId("myEntry", owner)
val emptySliceData = EntrySliceData()
val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ ->
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
index 6c0c652..8576573 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.framework.common
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,13 +30,14 @@
assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+ val nullPage = NullPageProvider.createSettingsPage()
val sppRepoNull =
- SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+ SettingsPageProviderRepository(emptyList(), listOf(nullPage))
assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
- assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+ assertThat(sppRepoNull.getAllRootPages()).contains(nullPage)
- val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
- val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+ val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
+ val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
val allRoots = sppRepo.getAllRootPages()
assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 7fa1e26..dc74a10 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -22,8 +22,9 @@
import androidx.navigation.navArgument
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.getUniquePageId
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,27 +36,25 @@
@Test
fun testNullPage() {
- val page = SettingsPage.createNull()
- assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+ val page = NullPageProvider.createSettingsPage()
+ assertThat(page.id).isEqualTo(genPageId("NULL"))
assertThat(page.sppName).isEqualTo("NULL")
assertThat(page.displayName).isEqualTo("NULL")
assertThat(page.buildRoute()).isEqualTo("NULL")
assertThat(page.isCreateBy("NULL")).isTrue()
assertThat(page.isCreateBy("Spp")).isFalse()
- assertThat(page.hasRuntimeParam()).isFalse()
assertThat(page.isBrowsable()).isFalse()
}
@Test
fun testRegularPage() {
- val page = SettingsPage.create("mySpp", "SppDisplayName")
- assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+ val page = createSettingsPage("mySpp", "SppDisplayName")
+ assertThat(page.id).isEqualTo(genPageId("mySpp"))
assertThat(page.sppName).isEqualTo("mySpp")
assertThat(page.displayName).isEqualTo("SppDisplayName")
assertThat(page.buildRoute()).isEqualTo("mySpp")
assertThat(page.isCreateBy("NULL")).isFalse()
assertThat(page.isCreateBy("mySpp")).isTrue()
- assertThat(page.hasRuntimeParam()).isFalse()
assertThat(page.isBrowsable()).isTrue()
}
@@ -67,7 +66,7 @@
)
val page = spaEnvironment.createPage("SppWithParam", arguments)
assertThat(page.id).isEqualTo(
- getUniquePageId(
+ genPageId(
"SppWithParam", listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
@@ -78,7 +77,6 @@
assertThat(page.displayName).isEqualTo("SppWithParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
assertThat(page.isCreateBy("SppWithParam")).isTrue()
- assertThat(page.hasRuntimeParam()).isFalse()
assertThat(page.isBrowsable()).isTrue()
}
@@ -91,7 +89,7 @@
)
val page = spaEnvironment.createPage("SppWithRtParam", arguments)
assertThat(page.id).isEqualTo(
- getUniquePageId(
+ genPageId(
"SppWithRtParam", listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
@@ -103,7 +101,6 @@
assertThat(page.displayName).isEqualTo("SppWithRtParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
- assertThat(page.hasRuntimeParam()).isTrue()
assertThat(page.isBrowsable()).isFalse()
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
index 1854728..f974cca 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
@@ -19,9 +19,10 @@
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.NullPageProvider
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth
import org.junit.Before
@@ -40,7 +41,7 @@
@Test
fun testCreateIntent() {
- val nullPage = SettingsPage.createNull()
+ val nullPage = NullPageProvider.createSettingsPage()
Truth.assertThat(nullPage.createIntent()).isNull()
Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
.isNull()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/UniqueIdTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/UniqueIdTest.kt
new file mode 100644
index 0000000..c17f83b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/UniqueIdTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class UniqueIdTest {
+ @Test
+ fun testUniquePageId() {
+ Truth.assertThat(genPageId("mySpp")).isEqualTo("1byojwa")
+
+ val parameter = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+ val arguments = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ )
+ Truth.assertThat(genPageId("mySppWithParam", parameter, arguments)).isEqualTo("1sz4pbq")
+
+ val parameter2 = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ )
+ val arguments2 = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ "rt_param" to "myRtStr",
+ )
+ Truth.assertThat(genPageId("mySppWithRtParam", parameter2, arguments2)).isEqualTo("ts6d8k")
+ }
+
+ @Test
+ fun testUniqueEntryId() {
+ val owner = createSettingsPage("mySpp")
+ val fromPage = createSettingsPage("fromSpp")
+ val toPage = createSettingsPage("toSpp")
+
+ Truth.assertThat(genEntryId("myEntry", owner)).isEqualTo("145pppn")
+ Truth.assertThat(genEntryId("myEntry", owner, fromPage = fromPage, toPage = toPage))
+ .isEqualTo("1m7jzew")
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 530d2ed..341a4a5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -25,10 +25,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.util.genEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -52,7 +52,7 @@
// Slice supported
val page = SppLayer2.createSettingsPage()
- val entryId = getUniqueEntryId("Layer2Entry1", page)
+ val entryId = genEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
@@ -61,7 +61,7 @@
assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
// Slice unsupported
- val entryId2 = getUniqueEntryId("Layer2Entry2", page)
+ val entryId2 = genEntryId("Layer2Entry2", page)
val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
@@ -73,7 +73,7 @@
SpaEnvironmentFactory.reset(spaEnvironment)
val page = SppLayer2.createSettingsPage()
- val entryId = getUniqueEntryId("Layer2Entry1", page)
+ val entryId = genEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
// build slice data first
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SettingsPageHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SettingsPageHelper.kt
new file mode 100644
index 0000000..caf4136
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SettingsPageHelper.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.tests.testutils
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.util.genPageId
+
+fun createSettingsPage(
+ sppName: String,
+ displayName: String? = null,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+): SettingsPage {
+ return SettingsPage(
+ id = genPageId(sppName, parameter, arguments),
+ sppName = sppName,
+ displayName = displayName ?: sppName,
+ parameter = parameter,
+ arguments = arguments
+ )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
deleted file mode 100644
index ce9b791..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
+++ /dev/null
@@ -1,48 +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.settingslib.spa.tests.testutils
-
-import android.os.Bundle
-import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsPage
-import com.android.settingslib.spa.framework.common.toHashId
-import com.android.settingslib.spa.framework.util.normalize
-
-fun getUniquePageId(
- name: String,
- parameter: List<NamedNavArgument> = emptyList(),
- arguments: Bundle? = null
-): String {
- val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
- return "$name:${normArguments?.toString()}".toHashId()
-}
-
-fun getUniquePageId(page: SettingsPage): String {
- return getUniquePageId(page.sppName, page.parameter, page.arguments)
-}
-
-fun getUniqueEntryId(
- name: String,
- owner: SettingsPage,
- fromPage: SettingsPage? = null,
- toPage: SettingsPage? = null
-): String {
- val ownerId = getUniquePageId(owner)
- val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
- val toId = if (toPage == null) "null" else getUniquePageId(toPage)
- return "$name:$ownerId($fromId-$toId)".toHashId()
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/OWNERS b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/OWNERS
new file mode 100644
index 0000000..09e7991
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+emilychuang@google.com
+ykhung@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 156993d..50f3713 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -32,7 +32,7 @@
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
@@ -209,7 +209,7 @@
@RouteListingPreference.Item.SubText
public int getSelectionBehavior() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
- ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_NONE;
+ ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_TRANSFER;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
index c966757..dc8a862 100644
--- a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
@@ -131,7 +131,7 @@
// Touch coordinates are with respect to the upper left corner, so reverse
// this calculation
float yRelativeToSensor = sensorY - touchY;
- var angleInRad = Math.atan2(yRelativeToSensor, xRelativeToSensor);
+ double angleInRad = Math.atan2(yRelativeToSensor, xRelativeToSensor);
// If the radians are negative, that means we are counting clockwise.
// So we need to add 360 degrees
if (angleInRad < 0.0) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c133097..bf9e428 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -130,6 +130,7 @@
Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
@@ -227,6 +228,7 @@
Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
- Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
+ Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 03921e1..f0bc1df 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -189,6 +189,8 @@
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
@@ -359,5 +361,6 @@
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SystemUI/res/drawable/media_output_status_edit_session.xml b/packages/SystemUI/res/drawable/media_output_status_edit_session.xml
new file mode 100644
index 0000000..0bd45ed
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_edit_session.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M1,20V17.2Q1,16.35 1.438,15.637Q1.875,14.925 2.6,14.55Q4.15,13.775 5.75,13.387Q7.35,13 9,13Q10.65,13 12.25,13.387Q13.85,13.775 15.4,14.55Q16.125,14.925 16.562,15.637Q17,16.35 17,17.2V20ZM19,20V17Q19,15.9 18.388,14.887Q17.775,13.875 16.65,13.15Q17.925,13.3 19.05,13.662Q20.175,14.025 21.15,14.55Q22.05,15.05 22.525,15.662Q23,16.275 23,17V20ZM9,12Q7.35,12 6.175,10.825Q5,9.65 5,8Q5,6.35 6.175,5.175Q7.35,4 9,4Q10.65,4 11.825,5.175Q13,6.35 13,8Q13,9.65 11.825,10.825Q10.65,12 9,12ZM19,8Q19,9.65 17.825,10.825Q16.65,12 15,12Q14.725,12 14.3,11.938Q13.875,11.875 13.6,11.8Q14.275,11 14.637,10.025Q15,9.05 15,8Q15,6.95 14.637,5.975Q14.275,5 13.6,4.2Q13.95,4.075 14.3,4.037Q14.65,4 15,4Q16.65,4 17.825,5.175Q19,6.35 19,8Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_session.xml b/packages/SystemUI/res/drawable/media_output_status_session.xml
new file mode 100644
index 0000000..1deba1e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_session.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,20V12H8V20ZM10,20V4H14V20ZM16,20V9H20V20Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index aaa372a..e39f1a9 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -18,6 +18,7 @@
<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
+ android:id="@+id/user_item"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
index d49b9f1..a650512 100644
--- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -152,5 +152,16 @@
android:button="@drawable/media_output_item_check_box"
android:visibility="gone"
/>
+ <ImageView
+ android:id="@+id/media_output_item_end_click_icon"
+ android:src="@drawable/media_output_status_edit_session"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:focusable="false"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:visibility="gone"/>
</FrameLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 7c86bc7..ad129e8 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -18,6 +18,7 @@
<!-- LinearLayout -->
<com.android.systemui.qs.tiles.UserDetailItemView
+ android:id="@+id/user_item"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index a748e29..7e9202c 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -149,6 +149,8 @@
app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="450dp"
+ app:layout_constraintHorizontal_bias="0"
>
<include layout="@layout/screenshot_work_profile_first_run" />
<include layout="@layout/screenshot_detection_notice" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index b7e2494..44c0e16 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -88,6 +88,7 @@
final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>();
WindowContainerToken pipTask = null;
WindowContainerToken recentsTask = null;
+ int recentsTaskId = -1;
for (int i = apps.length - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo taskInfo = apps[i].taskInfo;
if (apps[i].mode == MODE_CLOSING) {
@@ -106,8 +107,10 @@
// This task is for recents, keep it on top.
t.setLayer(apps[i].leash, info.getChanges().size() * 3 - i);
recentsTask = taskInfo.token;
+ recentsTaskId = taskInfo.taskId;
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
recentsTask = taskInfo.token;
+ recentsTaskId = taskInfo.taskId;
}
}
// Also make all the wallpapers opaque since we want the visible from the start
@@ -116,7 +119,7 @@
}
t.apply();
mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
- recentsTask, leashMap, mToken,
+ recentsTask, recentsTaskId, leashMap, mToken,
(info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0);
recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
new Rect());
@@ -154,6 +157,7 @@
private ArrayList<WindowContainerToken> mPausingTasks = null;
private WindowContainerToken mPipTask = null;
private WindowContainerToken mRecentsTask = null;
+ private int mRecentsTaskId = 0;
private TransitionInfo mInfo = null;
private ArrayList<SurfaceControl> mOpeningLeashes = null;
private boolean mOpeningHome = false;
@@ -167,8 +171,8 @@
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB,
ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask,
- WindowContainerToken recentsTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
- IBinder transition, boolean keyguardLocked) {
+ WindowContainerToken recentsTask, int recentsTaskId, ArrayMap<SurfaceControl,
+ SurfaceControl> leashMap, IBinder transition, boolean keyguardLocked) {
if (mInfo != null) {
throw new IllegalStateException("Trying to run a new recents animation while"
+ " recents is already active.");
@@ -179,6 +183,7 @@
mPausingTasks = pausingTasks;
mPipTask = pipTask;
mRecentsTask = recentsTask;
+ mRecentsTaskId = recentsTaskId;
mLeashMap = leashMap;
mTransition = transition;
mKeyguardLocked = keyguardLocked;
@@ -296,6 +301,15 @@
}
@Override public void setInputConsumerEnabled(boolean enabled) {
+ if (enabled) {
+ // transient launches don't receive focus automatically. Since we are taking over
+ // the gesture now, take focus explicitly.
+ try {
+ ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set focused task", e);
+ }
+ }
if (mWrapped != null) mWrapped.setInputConsumerEnabled(enabled);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 38fa354..54ae84f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -16,18 +16,20 @@
package com.android.keyguard
-import android.annotation.IntDef
import android.content.ContentResolver
import android.database.ContentObserver
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.net.Uri
import android.os.Handler
+import android.os.PowerManager
+import android.os.PowerManager.WAKE_REASON_UNFOLD_DEVICE
import android.os.UserHandle
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
@@ -52,23 +54,26 @@
companion object {
const val TAG = "ActiveUnlockConfig"
-
- const val BIOMETRIC_TYPE_NONE = 0
- const val BIOMETRIC_TYPE_ANY_FACE = 1
- const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2
- const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3
}
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT,
- BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)
- annotation class BiometricType
-
/**
* Indicates the origin for an active unlock request.
*/
- enum class ACTIVE_UNLOCK_REQUEST_ORIGIN {
- WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT
+ enum class ActiveUnlockRequestOrigin {
+ WAKE,
+ UNLOCK_INTENT,
+ BIOMETRIC_FAIL,
+ ASSISTANT,
+ }
+
+ /**
+ * Biometric type options.
+ */
+ enum class BiometricType(val intValue: Int) {
+ NONE(0),
+ ANY_FACE(1),
+ ANY_FINGERPRINT(2),
+ UNDER_DISPLAY_FINGERPRINT(3),
}
var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
@@ -76,9 +81,10 @@
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
- private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT)
+ private var faceErrorsToTriggerBiometricFailOn = mutableSetOf<Int>()
private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>()
- private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE)
+ private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>()
+ private var wakeupsConsideredUnlockIntents = mutableSetOf<Int>()
private val settingsObserver = object : ContentObserver(handler) {
private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
@@ -89,16 +95,19 @@
secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)
private val unlockIntentWhenBiometricEnrolledUri =
secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ private val wakeupsConsideredUnlockIntentsUri =
+ secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)
fun register() {
registerUri(
listOf(
- wakeUri,
- unlockIntentUri,
- bioFailUri,
- faceErrorsUri,
- faceAcquireInfoUri,
- unlockIntentWhenBiometricEnrolledUri
+ wakeUri,
+ unlockIntentUri,
+ bioFailUri,
+ faceErrorsUri,
+ faceAcquireInfoUri,
+ unlockIntentWhenBiometricEnrolledUri,
+ wakeupsConsideredUnlockIntentsUri,
)
)
@@ -153,7 +162,7 @@
secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
getCurrentUser()),
faceAcquireInfoToTriggerBiometricFailOn,
- setOf<Int>())
+ emptySet())
}
if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) {
@@ -162,7 +171,16 @@
ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
getCurrentUser()),
onUnlockIntentWhenBiometricEnrolled,
- setOf(BIOMETRIC_TYPE_NONE))
+ setOf(BiometricType.NONE.intValue))
+ }
+
+ if (selfChange || uris.contains(wakeupsConsideredUnlockIntentsUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ getCurrentUser()),
+ wakeupsConsideredUnlockIntents,
+ setOf(WAKE_REASON_UNFOLD_DEVICE))
}
}
@@ -181,10 +199,12 @@
out.clear()
stringSetting?.let {
for (code: String in stringSetting.split("|")) {
- try {
- out.add(code.toInt())
- } catch (e: NumberFormatException) {
- Log.e(TAG, "Passed an invalid setting=$code")
+ if (code.isNotEmpty()) {
+ try {
+ out.add(code.toInt())
+ } catch (e: NumberFormatException) {
+ Log.e(TAG, "Passed an invalid setting=$code")
+ }
}
}
} ?: out.addAll(default)
@@ -221,22 +241,30 @@
}
/**
+ * Whether the PowerManager wake reason is considered an unlock intent and should use origin
+ * [ActiveUnlockRequestOrigin.UNLOCK_INTENT] instead of [ActiveUnlockRequestOrigin.WAKE].
+ */
+ fun isWakeupConsideredUnlockIntent(pmWakeReason: Int): Boolean {
+ return wakeupsConsideredUnlockIntents.contains(pmWakeReason)
+ }
+
+ /**
* Whether to trigger active unlock based on where the request is coming from and
* the current settings.
*/
- fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ACTIVE_UNLOCK_REQUEST_ORIGIN): Boolean {
+ fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ActiveUnlockRequestOrigin): Boolean {
return when (requestOrigin) {
- ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup
+ ActiveUnlockRequestOrigin.WAKE -> requestActiveUnlockOnWakeup
- ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT ->
+ ActiveUnlockRequestOrigin.UNLOCK_INTENT ->
requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup ||
(shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment())
- ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL ->
+ ActiveUnlockRequestOrigin.BIOMETRIC_FAIL ->
requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent ||
requestActiveUnlockOnWakeup
- ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT -> isActiveUnlockEnabled()
+ ActiveUnlockRequestOrigin.ASSISTANT -> isActiveUnlockEnabled()
}
}
@@ -252,18 +280,18 @@
val udfpsEnrolled = it.isUdfpsEnrolled
if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
- return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE)
+ return onUnlockIntentWhenBiometricEnrolled.contains(BiometricType.NONE.intValue)
}
if (!anyFaceEnrolled && anyFingerprintEnrolled) {
return onUnlockIntentWhenBiometricEnrolled.contains(
- BIOMETRIC_TYPE_ANY_FINGERPRINT) ||
+ BiometricType.ANY_FINGERPRINT.intValue) ||
(udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains(
- BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT))
+ BiometricType.UNDER_DISPLAY_FINGERPRINT.intValue))
}
if (!anyFingerprintEnrolled && anyFaceEnrolled) {
- return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE)
+ return onUnlockIntentWhenBiometricEnrolled.contains(BiometricType.ANY_FACE.intValue)
}
}
@@ -275,11 +303,15 @@
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
- pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
- "$onUnlockIntentWhenBiometricEnrolled")
+ pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=${
+ onUnlockIntentWhenBiometricEnrolled.map { BiometricType.values()[it] }
+ }")
pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
"$faceAcquireInfoToTriggerBiometricFailOn")
+ pw.println(" activeUnlockWakeupsConsideredUnlockIntents=${
+ wakeupsConsideredUnlockIntents.map { PowerManager.wakeReasonToString(it) }
+ }")
pw.println("Current state:")
keyguardUpdateMonitor?.let {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3eec565..e55ac1b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -106,6 +106,12 @@
updateDoubleLineClock();
}
};
+ private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean change) {
+ setWeatherVisibility();
+ }
+ };
private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
mKeyguardUnlockAnimationListener =
@@ -216,7 +222,15 @@
UserHandle.USER_ALL
);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
+ false, /* notifyForDescendants */
+ mShowWeatherObserver,
+ UserHandle.USER_ALL
+ );
+
updateDoubleLineClock();
+ setWeatherVisibility();
mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
@@ -449,6 +463,14 @@
}
}
+ private void setWeatherVisibility() {
+ if (mWeatherView != null) {
+ mUiExecutor.execute(
+ () -> mWeatherView.setVisibility(
+ mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE));
+ }
+ }
+
/**
* Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
* bounds during the unlock transition.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 8011efd..92e3641 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -131,6 +131,7 @@
@Override
void resetState() {
+ mMessageAreaController.setMessage(getInitialMessageResId());
mView.setPasswordEntryEnabled(true);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57bfe54..20baa81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -237,7 +237,7 @@
}
if (mUpdateMonitor.isFaceEnrolled()) {
mUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"swipeUpOnBouncer");
}
}
@@ -247,6 +247,7 @@
@Override
public void onThemeChanged() {
reloadColors();
+ reset();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d23ea9e..ec56967 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1312,7 +1312,8 @@
}
public boolean getUserHasTrust(int userId) {
- return !isTrustDisabled() && mUserHasTrust.get(userId);
+ return !isTrustDisabled() && mUserHasTrust.get(userId)
+ && isUnlockingWithTrustAgentAllowed();
}
/**
@@ -1320,12 +1321,19 @@
*/
public boolean getUserUnlockedWithBiometric(int userId) {
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
- BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
&& isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
- boolean faceAllowed = face != null && face.mAuthenticated
+ return fingerprintAllowed || getUserUnlockedWithFace(userId);
+ }
+
+
+ /**
+ * Returns whether the user is unlocked with face.
+ */
+ public boolean getUserUnlockedWithFace(int userId) {
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+ return face != null && face.mAuthenticated
&& isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
- return fingerprintAllowed || faceAllowed;
}
/**
@@ -1400,6 +1408,10 @@
return mUserTrustIsUsuallyManaged.get(userId);
}
+ private boolean isUnlockingWithTrustAgentAllowed() {
+ return isUnlockingWithBiometricAllowed(true);
+ }
+
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
// StrongAuthTracker#isUnlockingWithBiometricAllowed includes
// STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
@@ -1535,7 +1547,7 @@
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
if (mAssistantVisible) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
"assistant",
false);
}
@@ -1665,7 +1677,7 @@
@Override
public void onAuthenticationFailed() {
requestActiveUnlockDismissKeyguard(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"fingerprintFailure");
handleFingerprintAuthFailed();
}
@@ -1734,7 +1746,7 @@
: mPrimaryBouncerFullyShown ? "bouncer"
: "udfpsFpDown";
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"faceFailure-" + reason);
handleFaceAuthFailed();
@@ -1761,7 +1773,7 @@
if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"faceError-" + errMsgId);
}
}
@@ -1773,7 +1785,7 @@
if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
acquireInfo)) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"faceAcquireInfo-" + acquireInfo);
}
}
@@ -1913,8 +1925,11 @@
FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_STARTED_WAKING_UP);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp - "
- + PowerManager.wakeReasonToString(pmWakeReason));
+ requestActiveUnlock(
+ mActiveUnlockConfig.isWakeupConsideredUnlockIntent(pmWakeReason)
+ ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
+ "wakingUp - " + PowerManager.wakeReasonToString(pmWakeReason));
} else {
mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
}
@@ -2478,7 +2493,7 @@
mAuthInterruptActive = active;
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "onReach");
+ requestActiveUnlock(ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE, "onReach");
}
/**
@@ -2548,7 +2563,7 @@
* Attempts to trigger active unlock from trust agent.
*/
private void requestActiveUnlock(
- @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
String reason,
boolean dismissKeyguard
) {
@@ -2559,7 +2574,7 @@
final boolean allowRequest =
mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(requestOrigin);
- if (requestOrigin == ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE
+ if (requestOrigin == ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
&& !allowRequest && mActiveUnlockConfig.isActiveUnlockEnabled()) {
// instead of requesting the active unlock, initiate the unlock
initiateActiveUnlock(reason);
@@ -2578,7 +2593,7 @@
* Only dismisses the keyguard under certain conditions.
*/
public void requestActiveUnlock(
- @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
String extraReason
) {
final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null
@@ -2595,7 +2610,7 @@
* Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard.
*/
public void requestActiveUnlockDismissKeyguard(
- @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
String extraReason
) {
requestActiveUnlock(
@@ -2612,7 +2627,7 @@
updateFaceListeningState(BIOMETRIC_ACTION_START,
FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN);
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"udfpsBouncer");
}
}
@@ -3418,7 +3433,7 @@
if (wasPrimaryBouncerFullyShown != mPrimaryBouncerFullyShown) {
if (mPrimaryBouncerFullyShown) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"bouncerFullyShown");
}
for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 201a1d9..c414c08 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -372,7 +372,7 @@
}
fun logUserRequestedUnlock(
- requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
+ requestOrigin: ActiveUnlockConfig.ActiveUnlockRequestOrigin,
reason: String?,
dismissKeyguard: Boolean
) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 4c1a9fa..b342a29 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -50,6 +50,7 @@
import android.widget.SeekBar;
import android.widget.Switch;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
@@ -139,8 +140,10 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
- // update persisted scale only when scale >= 2.0
- if (scale >= 2.0f) {
+ // Update persisted scale only when scale >= PERSISTED_SCALE_MIN_VALUE const.
+ // We assume if the scale is lower than the PERSISTED_SCALE_MIN_VALUE, there will be
+ // no obvious magnification effect.
+ if (scale >= MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index fb0c0a6..5ca36ab 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -72,7 +72,7 @@
height = WindowManager.LayoutParams.MATCH_PARENT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
format = PixelFormat.TRANSLUCENT
- type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
+ type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
fitInsetsTypes = 0 // Ignore insets from all system bars
title = "Wired Charging Animation"
flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 43fb39f..d8ceaea 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -85,7 +85,7 @@
// TODO(b/259217907)
@JvmField
val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
- unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+ releasedFlag(259217907, "notification_group_dismissal_animation")
// TODO(b/257506350): Tracking Bug
@JvmField val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
@@ -473,7 +473,7 @@
// TODO(b/254512728): Tracking Bug
@JvmField
- val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
+ val NEW_BACK_AFFORDANCE = releasedFlag(1203, "new_back_affordance")
// TODO(b/255854141): Tracking Bug
@JvmField
@@ -507,8 +507,7 @@
// 1300 - screenshots
// TODO(b/254513155): Tracking Bug
@JvmField
- val SCREENSHOT_WORK_PROFILE_POLICY =
- unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
+ val SCREENSHOT_WORK_PROFILE_POLICY = releasedFlag(1301, "screenshot_work_profile_policy")
// TODO(b/264916608): Tracking Bug
@JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
@@ -532,13 +531,20 @@
val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
// TODO(b/266983432) Tracking Bug
- val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions")
+ val SHARESHEET_CUSTOM_ACTIONS =
+ unreleasedFlag(1501, "sharesheet_custom_actions", teamfood = true)
// TODO(b/266982749) Tracking Bug
- val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action")
+ val SHARESHEET_RESELECTION_ACTION =
+ unreleasedFlag(1502, "sharesheet_reselection_action", teamfood = true)
// TODO(b/266983474) Tracking Bug
- val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
+ val SHARESHEET_IMAGE_AND_TEXT_PREVIEW =
+ unreleasedFlag(1503, "sharesheet_image_text_preview", teamfood = true)
+
+ // TODO(b/267355521) Tracking Bug
+ val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW =
+ unreleasedFlag(1504, "sharesheet_scrollable_image_preview")
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 3319f9d..ab009f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -381,82 +381,87 @@
return when (event?.actionMasked) {
MotionEvent.ACTION_DOWN ->
if (viewModel.configKey != null) {
- longPressAnimator =
- view
- .animate()
- .scaleX(PRESSED_SCALE)
- .scaleY(PRESSED_SCALE)
- .setDuration(longPressDurationMs)
- .withEndAction {
- view.setOnClickListener {
- vibratorHelper?.vibrate(
- if (viewModel.isActivated) {
- Vibrations.Activated
- } else {
- Vibrations.Deactivated
- }
- )
- viewModel.onClicked(
- KeyguardQuickAffordanceViewModel.OnClickedParameters(
- configKey = viewModel.configKey,
- expandable = Expandable.fromView(view),
- )
- )
+ if (isUsingAccurateTool(event)) {
+ // For accurate tool types (stylus, mouse, etc.), we don't require a
+ // long-press.
+ } else {
+ // When not using a stylus, we require a long-press to activate the
+ // quick affordance, mostly to do "falsing" (e.g. protect from false
+ // clicks in the pocket/bag).
+ longPressAnimator =
+ view
+ .animate()
+ .scaleX(PRESSED_SCALE)
+ .scaleY(PRESSED_SCALE)
+ .setDuration(longPressDurationMs)
+ .withEndAction {
+ dispatchClick(viewModel.configKey)
+ cancel()
}
- view.performClick()
- view.setOnClickListener(null)
- cancel()
- }
+ }
true
} else {
false
}
MotionEvent.ACTION_MOVE -> {
- if (event.historySize > 0) {
- val distance =
- sqrt(
- (event.y - event.getHistoricalY(0)).pow(2) +
- (event.x - event.getHistoricalX(0)).pow(2)
- )
- if (distance > ViewConfiguration.getTouchSlop()) {
+ if (!isUsingAccurateTool(event)) {
+ // Moving too far while performing a long-press gesture cancels that
+ // gesture.
+ val distanceMoved = distanceMoved(event)
+ if (distanceMoved > ViewConfiguration.getTouchSlop()) {
cancel()
}
}
true
}
MotionEvent.ACTION_UP -> {
- cancel(
- onAnimationEnd =
- if (event.eventTime - event.downTime < longPressDurationMs) {
- Runnable {
- messageDisplayer.invoke(
- R.string.keyguard_affordance_press_too_short
- )
- val amplitude =
- view.context.resources
- .getDimensionPixelSize(
- R.dimen.keyguard_affordance_shake_amplitude
- )
- .toFloat()
- val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- -amplitude / 2,
- amplitude / 2,
+ if (isUsingAccurateTool(event)) {
+ // When using an accurate tool type (stylus, mouse, etc.), we don't require
+ // a long-press gesture to activate the quick affordance. Therefore, lifting
+ // the pointer performs a click.
+ if (
+ viewModel.configKey != null &&
+ distanceMoved(event) <= ViewConfiguration.getTouchSlop()
+ ) {
+ dispatchClick(viewModel.configKey)
+ }
+ } else {
+ // When not using a stylus, lifting the finger/pointer will actually cancel
+ // the long-press gesture. Calling cancel after the quick affordance was
+ // already long-press activated is a no-op, so it's safe to call from here.
+ cancel(
+ onAnimationEnd =
+ if (event.eventTime - event.downTime < longPressDurationMs) {
+ Runnable {
+ messageDisplayer.invoke(
+ R.string.keyguard_affordance_press_too_short
)
- shakeAnimator.duration =
- ShakeAnimationDuration.inWholeMilliseconds
- shakeAnimator.interpolator =
- CycleInterpolator(ShakeAnimationCycles)
- shakeAnimator.start()
+ val amplitude =
+ view.context.resources
+ .getDimensionPixelSize(
+ R.dimen.keyguard_affordance_shake_amplitude
+ )
+ .toFloat()
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ -amplitude / 2,
+ amplitude / 2,
+ )
+ shakeAnimator.duration =
+ ShakeAnimationDuration.inWholeMilliseconds
+ shakeAnimator.interpolator =
+ CycleInterpolator(ShakeAnimationCycles)
+ shakeAnimator.start()
- vibratorHelper?.vibrate(Vibrations.Shake)
+ vibratorHelper?.vibrate(Vibrations.Shake)
+ }
+ } else {
+ null
}
- } else {
- null
- }
- )
+ )
+ }
true
}
MotionEvent.ACTION_CANCEL -> {
@@ -467,6 +472,28 @@
}
}
+ private fun dispatchClick(
+ configKey: String,
+ ) {
+ view.setOnClickListener {
+ vibratorHelper?.vibrate(
+ if (viewModel.isActivated) {
+ Vibrations.Activated
+ } else {
+ Vibrations.Deactivated
+ }
+ )
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = configKey,
+ expandable = Expandable.fromView(view),
+ )
+ )
+ }
+ view.performClick()
+ view.setOnClickListener(null)
+ }
+
private fun cancel(onAnimationEnd: Runnable? = null) {
longPressAnimator?.cancel()
longPressAnimator = null
@@ -475,6 +502,40 @@
companion object {
private const val PRESSED_SCALE = 1.5f
+
+ /**
+ * Returns `true` if the tool type at the given pointer index is an accurate tool (like
+ * stylus or mouse), which means we can trust it to not be a false click; `false`
+ * otherwise.
+ */
+ private fun isUsingAccurateTool(
+ event: MotionEvent,
+ pointerIndex: Int = 0,
+ ): Boolean {
+ return when (event.getToolType(pointerIndex)) {
+ MotionEvent.TOOL_TYPE_STYLUS -> true
+ MotionEvent.TOOL_TYPE_MOUSE -> true
+ else -> false
+ }
+ }
+
+ /**
+ * Returns the amount of distance the pointer moved since the historical record at the
+ * [since] index.
+ */
+ private fun distanceMoved(
+ event: MotionEvent,
+ since: Int = 0,
+ ): Float {
+ return if (event.historySize > 0) {
+ sqrt(
+ (event.y - event.getHistoricalY(since)).pow(2) +
+ (event.x - event.getHistoricalX(since)).pow(2)
+ )
+ } else {
+ 0f
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 9f5d372..3b45615 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -54,6 +54,8 @@
private static final String TAG = "MediaOutputAdapter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f;
+ private static final float DEVICE_CONNECTED_ALPHA = 1f;
public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
@@ -201,26 +203,48 @@
&& mController.isSubStatusSupported()
&& mController.isAdvancedLayoutSupported() && device.hasSubtext()) {
boolean isActiveWithOngoingSession =
- device.hasOngoingSession() && currentlyConnected;
- if (isActiveWithOngoingSession) {
- //Selected device which has ongoing session, disable seekbar since we
- //only allow volume control on Host
- mSeekBar.setVolume(0);
- disableSeekBar();
+ (device.hasOngoingSession() && currentlyConnected);
+ boolean isHost = mController.isVolumeControlEnabled(device)
+ && isActiveWithOngoingSession;
+ if (isHost) {
mCurrentActivePosition = position;
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ mSubTitleText.setText(device.getSubtextString());
+ updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA);
+ updateEndClickAreaAsSessionEditing(device);
+ setTwoLineLayout(device, null /* title */, true /* bFocused */,
+ true /* showSeekBar */, false /* showProgressBar */,
+ true /* showSubtitle */, false /* showStatus */,
+ true /* showEndTouchArea */, false /* isFakeActive */);
+ initSeekbar(device, isCurrentSeekbarInvisible);
+ } else {
+ if (isActiveWithOngoingSession) {
+ //Selected device which has ongoing session, disable seekbar since we
+ //only allow volume control on Host
+ initSeekbar(device, isCurrentSeekbarInvisible);
+ mCurrentActivePosition = position;
+ }
+ setUpDeviceIcon(device);
+ mSubTitleText.setText(device.getSubtextString());
+ Drawable deviceStatusIcon =
+ isActiveWithOngoingSession ? mContext.getDrawable(
+ R.drawable.media_output_status_session)
+ : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
+ device,
+ mContext);
+ if (deviceStatusIcon != null) {
+ updateDeviceStatusIcon(deviceStatusIcon);
+ }
+ updateTwoLineLayoutContentAlpha(
+ updateClickActionBasedOnSelectionBehavior(device)
+ ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
+ setTwoLineLayout(device, isActiveWithOngoingSession /* bFocused */,
+ isActiveWithOngoingSession /* showSeekBar */,
+ false /* showProgressBar */, true /* showSubtitle */,
+ deviceStatusIcon != null /* showStatus */,
+ isActiveWithOngoingSession /* isFakeActive */);
}
- setUpDeviceIcon(device);
- mSubTitleText.setText(device.getSubtextString());
- Drawable deviceStatusIcon =
- Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(device, mContext);
- if (deviceStatusIcon != null) {
- updateDeviceStatusIcon(deviceStatusIcon);
- }
- updateClickActionBasedOnSelectionBehavior(device);
- setTwoLineLayout(device, isActiveWithOngoingSession /* bFocused */,
- isActiveWithOngoingSession /* showSeekBar */,
- false /* showProgressBar */, true /* showSubtitle */,
- deviceStatusIcon != null /* showStatus */);
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
updateConnectionFailedStatusIcon();
@@ -228,7 +252,7 @@
updateFullItemClickListener(v -> onItemClick(v, device));
setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
false /* showProgressBar */, true /* showSubtitle */,
- true /* showStatus */);
+ true /* showStatus */, false /*isFakeActive*/);
} else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
setUpDeviceIcon(device);
updateProgressBarColor();
@@ -317,10 +341,35 @@
ColorStateList(states, colors));
}
- private void updateClickActionBasedOnSelectionBehavior(MediaDevice device) {
+ private void updateTwoLineLayoutContentAlpha(float alphaValue) {
+ mSubTitleText.setAlpha(alphaValue);
+ mTitleIcon.setAlpha(alphaValue);
+ mTwoLineTitleText.setAlpha(alphaValue);
+ mStatusIcon.setAlpha(alphaValue);
+ }
+
+ private void updateEndClickAreaAsSessionEditing(MediaDevice device) {
+ mEndClickIcon.setOnClickListener(null);
+ mEndTouchArea.setOnClickListener(null);
+ updateEndClickAreaColor(mController.getColorSeekbarProgress());
+ mEndClickIcon.setColorFilter(mController.getColorItemContent());
+ mEndClickIcon.setOnClickListener(
+ v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
+ mEndTouchArea.setOnClickListener(v -> mCheckBox.performClick());
+ }
+
+ public void updateEndClickAreaColor(int color) {
+ if (mController.isAdvancedLayoutSupported()) {
+ mEndTouchArea.getBackground().setColorFilter(
+ new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
+ }
+ }
+
+ private boolean updateClickActionBasedOnSelectionBehavior(MediaDevice device) {
View.OnClickListener clickListener = Api34Impl.getClickListenerBasedOnSelectionBehavior(
device, mController, v -> onItemClick(v, device));
updateFullItemClickListener(clickListener);
+ return clickListener != null;
}
private void updateConnectionFailedStatusIcon() {
@@ -457,7 +506,7 @@
case SELECTION_BEHAVIOR_GO_TO_APP:
return v -> controller.tryToLaunchInAppRoutingIntent(device.getId(), v);
}
- return null;
+ return defaultTransferListener;
}
@DoNotInline
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index b5e829e..b1cbee8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -147,6 +147,7 @@
final ImageView mStatusIcon;
final CheckBox mCheckBox;
final ViewGroup mEndTouchArea;
+ final ImageView mEndClickIcon;
@VisibleForTesting
MediaOutputSeekbar mSeekBar;
private String mDeviceId;
@@ -168,11 +169,13 @@
mCheckBox = view.requireViewById(R.id.check_box);
mEndTouchArea = view.requireViewById(R.id.end_action_area);
if (mController.isAdvancedLayoutSupported()) {
+ mEndClickIcon = view.requireViewById(R.id.media_output_item_end_click_icon);
mVolumeValueText = view.requireViewById(R.id.volume_value);
mIconAreaLayout = view.requireViewById(R.id.icon_area);
} else {
mVolumeValueText = null;
mIconAreaLayout = null;
+ mEndClickIcon = null;
}
initAnimator();
}
@@ -218,20 +221,7 @@
.mutate();
mItemLayout.setBackground(backgroundDrawable);
if (showSeekBar) {
- final ClipDrawable clipDrawable =
- (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
- .findDrawableByLayerId(android.R.id.progress);
- final GradientDrawable progressDrawable =
- (GradientDrawable) clipDrawable.getDrawable();
- if (mController.isAdvancedLayoutSupported()) {
- progressDrawable.setCornerRadii(
- new float[]{0, 0, mController.getActiveRadius(),
- mController.getActiveRadius(),
- mController.getActiveRadius(),
- mController.getActiveRadius(), 0, 0});
- } else {
- progressDrawable.setCornerRadius(mController.getActiveRadius());
- }
+ updateSeekbarProgressBackground();
}
}
mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
@@ -265,14 +255,15 @@
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
- boolean showProgressBar, boolean showSubtitle, boolean showStatus) {
+ boolean showProgressBar, boolean showSubtitle, boolean showStatus,
+ boolean isFakeActive) {
setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle,
- showStatus);
+ showStatus, false, isFakeActive);
}
- private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
+ void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
boolean showSeekBar, boolean showProgressBar, boolean showSubtitle,
- boolean showStatus) {
+ boolean showStatus , boolean showEndTouchArea, boolean isFakeActive) {
mTitleText.setVisibility(View.GONE);
mTwoLineLayout.setVisibility(View.VISIBLE);
mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
@@ -287,12 +278,21 @@
showSeekBar ? mController.getColorConnectedItemBackground()
: mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN));
mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
- showSeekBar ? mController.getColorConnectedItemBackground()
+ showProgressBar || isFakeActive
+ ? mController.getColorConnectedItemBackground()
+ : showSeekBar ? mController.getColorSeekbarProgress()
: mController.getColorItemBackground(),
PorterDuff.Mode.SRC_IN));
+ if (showSeekBar) {
+ updateSeekbarProgressBackground();
+ }
+ //update end click area by isActive
+ mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+ mEndClickIcon.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
- params.rightMargin = mController.getItemMarginEndDefault();
+ params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
+ : mController.getItemMarginEndDefault();
} else {
backgroundDrawable = mContext.getDrawable(
R.drawable.media_output_item_background)
@@ -312,6 +312,23 @@
Typeface.NORMAL));
}
+ void updateSeekbarProgressBackground() {
+ final ClipDrawable clipDrawable =
+ (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
+ .findDrawableByLayerId(android.R.id.progress);
+ final GradientDrawable progressDrawable =
+ (GradientDrawable) clipDrawable.getDrawable();
+ if (mController.isAdvancedLayoutSupported()) {
+ progressDrawable.setCornerRadii(
+ new float[]{0, 0, mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ mController.getActiveRadius(), 0, 0});
+ } else {
+ progressDrawable.setCornerRadius(mController.getActiveRadius());
+ }
+ }
+
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
if (!mController.isVolumeControlEnabled(device)) {
disableSeekBar();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index d234dff..2aedd36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -704,19 +704,21 @@
devices.removeAll(targetMediaDevices);
targetMediaDevices.addAll(devices);
}
- mMediaItemList.clear();
- mMediaItemList.addAll(
- targetMediaDevices.stream().map(MediaItem::new).collect(Collectors.toList()));
+ List<MediaItem> finalMediaItems = targetMediaDevices.stream().map(
+ MediaItem::new).collect(Collectors.toList());
dividerItems.forEach((key, item) -> {
- mMediaItemList.add(key, item);
+ finalMediaItems.add(key, item);
});
- mMediaItemList.add(new MediaItem());
+ finalMediaItems.add(new MediaItem());
+ mMediaItemList.clear();
+ mMediaItemList.addAll(finalMediaItems);
}
}
private void categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices,
boolean needToHandleMutingExpectedDevice) {
synchronized (mMediaDevicesLock) {
+ List<MediaItem> finalMediaItems = new ArrayList<>();
Set<String> selectedDevicesIds = getSelectedMediaDevice().stream().map(
MediaDevice::getId).collect(Collectors.toSet());
if (connectedMediaDevice != null) {
@@ -726,32 +728,32 @@
boolean displayGroupAdded = false;
for (MediaDevice device : devices) {
if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) {
- mMediaItemList.add(0, new MediaItem(device));
+ finalMediaItems.add(0, new MediaItem(device));
} else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains(
device.getId())) {
- mMediaItemList.add(0, new MediaItem(device));
+ finalMediaItems.add(0, new MediaItem(device));
} else {
if (device.isSuggestedDevice() && !suggestedDeviceAdded) {
- attachGroupDivider(mContext.getString(
+ attachGroupDivider(finalMediaItems, mContext.getString(
R.string.media_output_group_title_suggested_device));
suggestedDeviceAdded = true;
} else if (!device.isSuggestedDevice() && !displayGroupAdded) {
- attachGroupDivider(mContext.getString(
+ attachGroupDivider(finalMediaItems, mContext.getString(
R.string.media_output_group_title_speakers_and_displays));
displayGroupAdded = true;
}
- mMediaItemList.add(new MediaItem(device));
+ finalMediaItems.add(new MediaItem(device));
}
}
- mMediaItemList.add(new MediaItem());
+ finalMediaItems.add(new MediaItem());
+ mMediaItemList.clear();
+ mMediaItemList.addAll(finalMediaItems);
}
}
- private void attachGroupDivider(String title) {
- synchronized (mMediaDevicesLock) {
- mMediaItemList.add(
- new MediaItem(title, MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
- }
+ private void attachGroupDivider(List<MediaItem> mediaItems, String title) {
+ mediaItems.add(
+ new MediaItem(title, MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
}
private void attachRangeInfo(List<MediaDevice> devices) {
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 ae37fb6..e0ba543 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -30,6 +30,7 @@
import android.view.VelocityTracker
import android.view.ViewConfiguration
import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
@@ -53,24 +54,24 @@
private const val PX_PER_SEC = 1000
private const val PX_PER_MS = 1
+internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
-private const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
private const val FAILSAFE_DELAY_MS = 350L
private const val POP_ON_FLING_DELAY = 160L
-private val VIBRATE_ACTIVATED_EFFECT =
+internal val VIBRATE_ACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
-private val VIBRATE_DEACTIVATED_EFFECT =
+internal val VIBRATE_DEACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
private const val DEBUG = false
-class BackPanelController private constructor(
+class BackPanelController internal constructor(
context: Context,
private val windowManager: WindowManager,
private val viewConfiguration: ViewConfiguration,
@@ -115,8 +116,10 @@
}
}
- private var params: EdgePanelParams = EdgePanelParams(resources)
- private var currentState: GestureState = GestureState.GONE
+ @VisibleForTesting
+ internal var params: EdgePanelParams = EdgePanelParams(resources)
+ @VisibleForTesting
+ internal var currentState: GestureState = GestureState.GONE
private var previousState: GestureState = GestureState.GONE
// Screen attributes
@@ -160,7 +163,7 @@
private val failsafeRunnable = Runnable { onFailsafe() }
- private enum class GestureState {
+ internal enum class GestureState {
/* Arrow is off the screen and invisible */
GONE,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6ee0a46..c573080 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4125,7 +4125,7 @@
if (didFaceAuthRun) {
mUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"lockScreenEmptySpaceTap");
} else {
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 1c4e319..8d68bce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -26,6 +26,7 @@
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
@@ -551,23 +552,23 @@
.build(),
true
);
- if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
- mRotateTextViewController.updateIndication(
- INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
- new KeyguardIndication.Builder()
- .setMessage(mBiometricMessageFollowUp)
- .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
- .setTextColor(mInitialTextColorState)
- .build(),
- true
- );
- } else {
- mRotateTextViewController.hideIndication(
- INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
- }
} else {
- mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
- mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
+ mRotateTextViewController.hideIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ }
+ if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
+ mRotateTextViewController.updateIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ new KeyguardIndication.Builder()
+ .setMessage(mBiometricMessageFollowUp)
+ .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
+ .setTextColor(mInitialTextColorState)
+ .build(),
+ true
+ );
+ } else {
+ mRotateTextViewController.hideIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
}
}
@@ -796,7 +797,8 @@
*/
private void showBiometricMessage(CharSequence biometricMessage,
@Nullable CharSequence biometricMessageFollowUp) {
- if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
+ if (TextUtils.equals(biometricMessage, mBiometricMessage)
+ && TextUtils.equals(biometricMessageFollowUp, mBiometricMessageFollowUp)) {
return;
}
@@ -805,7 +807,8 @@
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
hideBiometricMessageDelayed(
- mBiometricMessageFollowUp != null
+ !TextUtils.isEmpty(mBiometricMessage)
+ && !TextUtils.isEmpty(mBiometricMessageFollowUp)
? IMPORTANT_MSG_MIN_DURATION * 2
: DEFAULT_HIDE_DELAY_MS
);
@@ -1103,6 +1106,8 @@
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
final boolean faceAuthFailed = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
+ final boolean fpAuthFailed = biometricSourceType == FINGERPRINT
+ && msgId == BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; // ran matcher & failed
final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
@@ -1125,6 +1130,22 @@
mContext.getString(R.string.keyguard_face_failed),
mContext.getString(R.string.keyguard_suggest_fingerprint)
);
+ } else if (fpAuthFailed
+ && mKeyguardUpdateMonitor.getUserUnlockedWithFace(getCurrentUser())) {
+ // face had already previously unlocked the device, so instead of showing a
+ // fingerprint error, tell them they have already unlocked with face auth
+ // and how to enter their device
+ showBiometricMessage(
+ mContext.getString(R.string.keyguard_face_successful_unlock),
+ mContext.getString(R.string.keyguard_unlock)
+ );
+ } else if (fpAuthFailed
+ && mKeyguardUpdateMonitor.getUserHasTrust(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ showBiometricMessage(
+ getTrustGrantedIndication(),
+ mContext.getString(R.string.keyguard_unlock)
+ );
} else {
showBiometricMessage(helpString);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index fe76c7d..81c7197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -31,6 +31,7 @@
import android.os.UserHandle
import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS
+import android.provider.Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.View
@@ -245,6 +246,17 @@
datePlugin != null && weatherPlugin != null
}
+ fun isWeatherEnabled(): Boolean {
+ execution.assertIsMainThread()
+ val defaultValue = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault)
+ val showWeather = secureSettings.getIntForUser(
+ LOCK_SCREEN_WEATHER_ENABLED,
+ if (defaultValue) 1 else 0,
+ userTracker.userId) == 1
+ return showWeather
+ }
+
private fun updateBypassEnabled() {
val bypassEnabled = bypassController.bypassEnabled
smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 4550cb2..8ee2c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -76,7 +76,7 @@
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
keyguardUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
"KeyguardLiftController")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 5479b92..85729c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,16 +20,21 @@
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
/** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState(@DataState val dataState: Int) {
- Connected(DATA_CONNECTED),
- Connecting(DATA_CONNECTING),
- Disconnected(DATA_DISCONNECTED),
- Disconnecting(DATA_DISCONNECTING),
- Unknown(DATA_UNKNOWN),
+enum class DataConnectionState {
+ Connected,
+ Connecting,
+ Disconnected,
+ Disconnecting,
+ Suspended,
+ HandoverInProgress,
+ Unknown,
+ Invalid,
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -38,6 +43,8 @@
DATA_CONNECTING -> DataConnectionState.Connecting
DATA_DISCONNECTED -> DataConnectionState.Disconnected
DATA_DISCONNECTING -> DataConnectionState.Disconnecting
+ DATA_SUSPENDED -> DataConnectionState.Suspended
+ DATA_HANDOVER_IN_PROGRESS -> DataConnectionState.HandoverInProgress
DATA_UNKNOWN -> DataConnectionState.Unknown
- else -> throw IllegalArgumentException("unknown data state received $this")
+ else -> DataConnectionState.Invalid
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 012b9ec..ed7f60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -26,6 +26,7 @@
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
@@ -94,7 +95,7 @@
) : Diffable<MobileConnectionModel> {
override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
if (prevVal.dataConnectionState != dataConnectionState) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+ row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
}
if (prevVal.isEmergencyOnly != isEmergencyOnly) {
@@ -125,8 +126,12 @@
row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
}
- if (prevVal.dataActivityDirection != dataActivityDirection) {
- row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+ if (prevVal.dataActivityDirection.hasActivityIn != dataActivityDirection.hasActivityIn) {
+ row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
+ }
+
+ if (prevVal.dataActivityDirection.hasActivityOut != dataActivityDirection.hasActivityOut) {
+ row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
}
if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
@@ -139,7 +144,7 @@
}
override fun logFull(row: TableRowLogger) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+ row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
row.logChange(COL_EMERGENCY, isEmergencyOnly)
row.logChange(COL_ROAMING, isRoaming)
row.logChange(COL_OPERATOR, operatorAlphaShort)
@@ -147,11 +152,13 @@
row.logChange(COL_IS_GSM, isGsm)
row.logChange(COL_CDMA_LEVEL, cdmaLevel)
row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+ row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
+ row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
}
+ @VisibleForTesting
companion object {
const val COL_EMERGENCY = "EmergencyOnly"
const val COL_ROAMING = "Roaming"
@@ -161,7 +168,8 @@
const val COL_CDMA_LEVEL = "CdmaLevel"
const val COL_PRIMARY_LEVEL = "PrimaryLevel"
const val COL_CONNECTION_STATE = "ConnectionState"
- const val COL_ACTIVITY_DIRECTION = "DataActivity"
+ const val COL_ACTIVITY_DIRECTION_IN = "DataActivity.In"
+ const val COL_ACTIVITY_DIRECTION_OUT = "DataActivity.Out"
const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 8669047..c45b420 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -95,7 +95,7 @@
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
- columnName = "isWifiEnabled",
+ columnName = "isEnabled",
initialValue = wifiManager.isWifiEnabled,
)
.stateIn(
@@ -141,7 +141,7 @@
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
- columnName = "isWifiDefault",
+ columnName = "isDefault",
initialValue = false,
)
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
@@ -212,7 +212,7 @@
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
- columnPrefix = "wifiNetwork",
+ columnPrefix = "",
initialValue = WIFI_NETWORK_DEFAULT,
)
// There will be multiple wifi icons in different places that will frequently
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index e491d2b..094bcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -53,4 +53,4 @@
}
}
-private const val COL_ICON = "wifiIcon"
+private const val COL_ICON = "icon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index cc6fdcc..9ad36fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -438,6 +438,11 @@
}
@Override
+ public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
+ update(false /* updateAlways */);
+ }
+
+ @Override
public void onKeyguardVisibilityChanged(boolean visible) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index e5ab473..5cf01af 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -76,6 +76,9 @@
/** Whether user switching is currently in progress. */
val userSwitchingInProgress: Flow<Boolean>
+ /** User ID of the main user. */
+ val mainUserId: Int
+
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
@@ -130,7 +133,9 @@
private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
- override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+ override var mainUserId: Int = UserHandle.USER_NULL
+ private set
+ override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
private set
override val isGuestUserAutoCreated: Boolean =
@@ -172,6 +177,11 @@
// The guest user is always last, regardless of creation time.
.sortedBy { it.isGuest }
}
+
+ if (mainUserId == UserHandle.USER_NULL) {
+ val mainUser = withContext(backgroundDispatcher) { manager.mainUser }
+ mainUser?.let { mainUserId = it.identifier }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index a374885..0a07439 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -139,11 +139,11 @@
}
applicationScope.launch {
- var newUserId = UserHandle.USER_SYSTEM
+ var newUserId = repository.mainUserId
if (targetUserId == UserHandle.USER_NULL) {
// When a target user is not specified switch to last non guest user:
val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
- if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
+ if (lastSelectedNonGuestUserHandle != repository.mainUserId) {
val info =
withContext(backgroundDispatcher) {
manager.getUserInfo(lastSelectedNonGuestUserHandle)
@@ -215,8 +215,11 @@
// Create a new guest in the foreground, and then immediately switch to it
val newGuestId = create(showDialog, dismissDialog)
if (newGuestId == UserHandle.USER_NULL) {
- Log.e(TAG, "Could not create new guest, switching back to system user")
- switchUser(UserHandle.USER_SYSTEM)
+ Log.e(TAG, "Could not create new guest, switching back to main user")
+ val mainUser = withContext(backgroundDispatcher) { manager.mainUser?.identifier }
+
+ mainUser?.let { switchUser(it) }
+
withContext(backgroundDispatcher) {
manager.removeUserWhenPossible(
UserHandle.of(currentUser.id),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 39cc34b..e8d50ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -21,6 +21,8 @@
import android.hardware.biometrics.BiometricFaceConstants
import android.net.Uri
import android.os.Handler
+import android.os.PowerManager
+import android.os.PowerManager.WAKE_REASON_BIOMETRIC
import android.os.UserHandle
import android.provider.Settings
import androidx.test.filters.SmallTest
@@ -48,6 +50,8 @@
private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build()
private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build()
private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build()
+ private val fakeWakeupsConsideredUnlockIntents =
+ Uri.Builder().appendPath("wakeups-considered-unlock-intent").build()
@Mock
private lateinit var secureSettings: SecureSettings
@@ -82,6 +86,9 @@
`when`(secureSettings.getUriFor(
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED))
.thenReturn(fakeUnlockIntentBioEnroll)
+ `when`(secureSettings.getUriFor(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
+ .thenReturn(fakeWakeupsConsideredUnlockIntents)
activeUnlockConfig = ActiveUnlockConfig(
handler,
@@ -92,18 +99,18 @@
}
@Test
- fun testRegsitersForSettingsChanges() {
+ fun registersForSettingsChanges() {
verifyRegisterSettingObserver()
}
@Test
- fun testOnWakeupSettingChanged() {
+ fun onWakeupSettingChanged() {
verifyRegisterSettingObserver()
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
)
// WHEN unlock on wake is allowed
@@ -114,26 +121,26 @@
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
)
assertTrue(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
)
assertTrue(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)
)
}
@Test
- fun testOnUnlockIntentSettingChanged() {
+ fun onUnlockIntentSettingChanged() {
verifyRegisterSettingObserver()
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
)
// WHEN unlock on biometric failed is allowed
@@ -143,15 +150,15 @@
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
}
@Test
- fun testOnBioFailSettingChanged() {
+ fun onBioFailSettingChanged() {
verifyRegisterSettingObserver()
// GIVEN no active unlock settings enabled and triggering unlock intent on biometric
@@ -161,7 +168,7 @@
0)).thenReturn("")
updateSetting(fakeUnlockIntentBioEnroll)
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
// WHEN unlock on biometric failed is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
@@ -170,15 +177,15 @@
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
}
@Test
- fun testFaceErrorSettingsChanged() {
+ fun faceErrorSettingsChanged() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -200,7 +207,7 @@
}
@Test
- fun testFaceAcquiredSettingsChanged() {
+ fun faceAcquiredSettingsChanged() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -228,7 +235,7 @@
}
@Test
- fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() {
+ fun triggerOnUnlockIntentWhenBiometricEnrolledNone() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -244,16 +251,16 @@
// WHEN unlock intent is allowed when NO biometrics are enrolled (0)
`when`(secureSettings.getStringForUser(
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}")
+ 0)).thenReturn("${ActiveUnlockConfig.BiometricType.NONE.intValue}")
updateSetting(fakeUnlockIntentBioEnroll)
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
}
@Test
- fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
+ fun triggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -263,7 +270,7 @@
// GIVEN fingerprint and face are both enrolled
activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
// WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
@@ -271,29 +278,99 @@
`when`(secureSettings.getStringForUser(
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
0)).thenReturn(
- "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" +
- "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}")
+ "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
+ "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}")
updateSetting(fakeUnlockIntentBioEnroll)
// THEN active unlock triggers NOT allowed on unlock intent
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
// WHEN fingerprint ONLY enrolled
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
// WHEN face ONLY enrolled
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+ }
+
+ @Test
+ fun isWakeupConsideredUnlockIntent_singleValue() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN lift is considered an unlock intent
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ 0)).thenReturn(PowerManager.WAKE_REASON_LIFT.toString())
+ updateSetting(fakeWakeupsConsideredUnlockIntents)
+
+ // THEN only WAKE_REASON_LIFT is considered an unlock intent
+ for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
+ if (wakeReason == PowerManager.WAKE_REASON_LIFT) {
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ } else {
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ }
+ }
+ }
+
+ @Test
+ fun isWakeupConsideredUnlockIntent_multiValue() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN lift and tap are considered an unlock intent
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ 0)).thenReturn(
+ PowerManager.WAKE_REASON_LIFT.toString() +
+ "|" +
+ PowerManager.WAKE_REASON_TAP.toString()
+ )
+ updateSetting(fakeWakeupsConsideredUnlockIntents)
+
+ // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
+ for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
+ if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
+ wakeReason == PowerManager.WAKE_REASON_TAP) {
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ } else {
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ }
+ }
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT))
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+ }
+
+ @Test
+ fun isWakeupConsideredUnlockIntent_emptyValues() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN lift and tap are considered an unlock intent
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ 0)).thenReturn(" ")
+ updateSetting(fakeWakeupsConsideredUnlockIntents)
+
+ // THEN no wake up gestures are considered an unlock intent
+ for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ }
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_LIFT))
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE))
}
private fun updateSetting(uri: Uri) {
@@ -312,6 +389,7 @@
verifyRegisterSettingObserver(fakeFaceErrorsUri)
verifyRegisterSettingObserver(fakeFaceAcquiredUri)
verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
+ verifyRegisterSettingObserver(fakeWakeupsConsideredUnlockIntents)
}
private fun verifyRegisterSettingObserver(uri: Uri) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 36b3f89..ccc4e4a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -300,8 +300,9 @@
ArgumentCaptor<ContentObserver> observerCaptor =
ArgumentCaptor.forClass(ContentObserver.class);
mController.init();
- verify(mSecureSettings).registerContentObserverForUser(any(String.class),
- anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+ anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
ContentObserver observer = observerCaptor.getValue();
mExecutor.runAllReady();
@@ -347,6 +348,22 @@
assertEquals(0, mController.getClockBottom(10));
}
+ @Test
+ public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() {
+ when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+ ArgumentCaptor<ContentObserver> observerCaptor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ mController.init();
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
+ observerCaptor.capture(), eq(UserHandle.USER_ALL));
+ ContentObserver observer = observerCaptor.getValue();
+ mExecutor.runAllReady();
+ // When a settings change has occurred, check that view is visible.
+ observer.onChange(true);
+ mExecutor.runAllReady();
+ assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
+ }
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d912793..082c8cc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -134,4 +134,10 @@
keyguardPasswordViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
}
+
+ @Test
+ fun testMessageIsSetWhenReset() {
+ keyguardPasswordViewController.resetState()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index b742100..0881e61 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -120,4 +120,10 @@
public void testGetInitialMessageResId() {
assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0);
}
+
+ @Test
+ public void testMessageIsSetWhenReset() {
+ mKeyguardPinViewController.resetState();
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 075ef9d..0d65f12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -583,6 +583,8 @@
verify(mKeyguardSecurityViewFlipperController).clearViews();
verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
any(KeyguardSecurityCallback.class));
+ verify(mView).reset();
+ verify(mKeyguardSecurityViewFlipperController).reset();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4d95a22..ed9b5cf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -685,12 +685,36 @@
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
- // THEN unlocking with fingeprint is not allowed
+ // THEN unlocking with fingerprint is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
BiometricSourceType.FINGERPRINT));
}
@Test
+ public void trustAgentHasTrust() {
+ // WHEN user has trust
+ mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+
+ // THEN user is considered as "having trust" and bouncer can be skipped
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+ }
+
+ @Test
+ public void trustAgentHasTrust_fingerprintLockout() {
+ // GIVEN user has trust
+ mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+
+ // WHEN fingerprint is locked out
+ fingerprintErrorLockedOut();
+
+ // THEN user is NOT considered as "having trust" and bouncer cannot be skipped
+ Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+ Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+ }
+
+ @Test
public void testTriesToAuthenticate_whenBouncer() {
setKeyguardBouncerVisibility(true);
@@ -2182,7 +2206,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN fingerprint fails
@@ -2205,7 +2229,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN face fails & bypass is not allowed
@@ -2229,7 +2253,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN face fails & bypass is not allowed
@@ -2251,7 +2275,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN face fails & on the bouncer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 003af80f..f779845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -436,6 +436,34 @@
}
@Test
+ public void subStatusSupported_onBindViewHolder_bindHostDeviceWithOngoingSession_verifyView() {
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ when(mMediaDevice1.hasSubtext()).thenReturn(true);
+ when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
+ when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
+ when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
+ when(mMediaDevice1.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT);
+ assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+ TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse();
+ }
+
+ @Test
public void subStatusSupported_onBindViewHolder_bindDeviceRequirePremium_verifyView() {
String deviceStatus = (String) mContext.getText(
R.string.media_output_status_require_premium);
@@ -505,7 +533,7 @@
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
new file mode 100644
index 0000000..f42bfb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.gestural
+
+import android.os.Handler
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import android.view.ViewConfiguration
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.NavigationEdgeBackPlugin
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BackPanelControllerTest : SysuiTestCase() {
+ companion object {
+ private const val START_X: Float = 0f
+ }
+ private lateinit var mBackPanelController: BackPanelController
+ private lateinit var testableLooper: TestableLooper
+ private var triggerThreshold: Float = 0.0f
+ private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var layoutParams: WindowManager.LayoutParams
+ @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mBackPanelController =
+ BackPanelController(
+ context,
+ windowManager,
+ ViewConfiguration.get(context),
+ Handler.createAsync(Looper.myLooper()),
+ vibratorHelper,
+ configurationController,
+ latencyTracker
+ )
+ mBackPanelController.setLayoutParams(layoutParams)
+ mBackPanelController.setBackCallback(backCallback)
+ mBackPanelController.setIsLeftPanel(true)
+ testableLooper = TestableLooper.get(this)
+ triggerThreshold = mBackPanelController.params.staticTriggerThreshold
+ }
+
+ @Test
+ fun handlesActionDown() {
+ startTouch()
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.GONE)
+ }
+
+ @Test
+ fun staysHiddenBeforeSlopCrossed() {
+ startTouch()
+ // Move just enough to not cross the touch slop
+ continueTouch(START_X + touchSlop - 1)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.GONE)
+ }
+
+ @Test
+ fun handlesDragSlopCrossed() {
+ startTouch()
+ continueTouch(START_X + touchSlop + 1)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.ENTRY)
+ }
+
+ @Test
+ fun handlesBackCommitted() {
+ startTouch()
+ // Move once to cross the touch slop
+ continueTouch(START_X + touchSlop.toFloat() + 1)
+ // Move again to cross the back trigger threshold
+ continueTouch(START_X + touchSlop + triggerThreshold + 1)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.ACTIVE)
+ verify(backCallback).setTriggerBack(true)
+ testableLooper.moveTimeForward(100)
+ testableLooper.processAllMessages()
+ verify(vibratorHelper).vibrate(VIBRATE_ACTIVATED_EFFECT)
+
+ finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.FLUNG)
+ verify(backCallback).triggerBack()
+ }
+
+ @Test
+ fun handlesBackCancelled() {
+ startTouch()
+ continueTouch(START_X + touchSlop.toFloat() + 1)
+ continueTouch(
+ START_X + touchSlop + triggerThreshold -
+ mBackPanelController.params.deactivationSwipeTriggerThreshold
+ )
+ clearInvocations(backCallback)
+ Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION)
+ // Move in the opposite direction to cross the deactivation threshold and cancel back
+ continueTouch(START_X)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.INACTIVE)
+ verify(backCallback).setTriggerBack(false)
+ verify(vibratorHelper).vibrate(VIBRATE_DEACTIVATED_EFFECT)
+
+ finishTouchActionUp(START_X)
+ verify(backCallback).cancelBack()
+ }
+
+ private fun startTouch() {
+ mBackPanelController.onMotionEvent(createMotionEvent(ACTION_DOWN, START_X, 0f))
+ }
+
+ private fun continueTouch(x: Float) {
+ mBackPanelController.onMotionEvent(createMotionEvent(ACTION_MOVE, x, 0f))
+ }
+
+ private fun finishTouchActionUp(x: Float) {
+ mBackPanelController.onMotionEvent(createMotionEvent(ACTION_UP, x, 0f))
+ }
+
+ private fun createMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
+ return MotionEvent.obtain(0L, 0L, action, x, y, 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 0806a62..1ac7eaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -631,6 +631,82 @@
}
@Test
+ public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
+ createController();
+
+ // GIVEN face has already unlocked the device
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithFace(anyInt())).thenReturn(true);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a fingerprint not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN show sequential messages such as: 'Unlocked by face' and
+ // 'Swipe up to open'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_successful_unlock));
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricHelp_coEx_fpFailure_trustAgentAlreadyUnlocked() {
+ createController();
+
+ // GIVEN trust agent has already unlocked the device
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a fingerprint not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN show sequential messages such as: 'Kept unlocked by TrustAgent' and
+ // 'Swipe up to open'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_indication_trust_unlocked));
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricHelp_coEx_fpFailure_trustAgentUnlocked_emptyTrustGrantedMessage() {
+ createController();
+
+ // GIVEN trust agent has already unlocked the device & trust granted message is empty
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ mController.showTrustGrantedMessage(false, "");
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a fingerprint not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN show action to unlock (ie: 'Swipe up to open')
+ verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
createController();
String message = mContext.getString(R.string.keyguard_unlock);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
index f822ba0..45189cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
@@ -19,7 +19,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_IN
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_OUT
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
@@ -54,7 +55,19 @@
assertThat(logger.changes)
.contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
assertThat(logger.changes)
- .contains(Pair(COL_ACTIVITY_DIRECTION, connection.dataActivityDirection.toString()))
+ .contains(
+ Pair(
+ COL_ACTIVITY_DIRECTION_IN,
+ connection.dataActivityDirection.hasActivityIn.toString(),
+ )
+ )
+ assertThat(logger.changes)
+ .contains(
+ Pair(
+ COL_ACTIVITY_DIRECTION_OUT,
+ connection.dataActivityDirection.hasActivityOut.toString(),
+ )
+ )
assertThat(logger.changes)
.contains(
Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index d6b8c0d..314e250 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -41,6 +41,8 @@
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.ERI_ON
@@ -255,6 +257,37 @@
}
@Test
+ fun testFlowForSubId_dataConnectionState_suspended() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState)
+ .isEqualTo(DataConnectionState.HandoverInProgress)
+
+ job.cancel()
+ }
+
+ @Test
fun testFlowForSubId_dataConnectionState_unknown() =
runBlocking(IMMEDIATE) {
var latest: MobileConnectionModel? = null
@@ -270,6 +303,21 @@
}
@Test
+ fun testFlowForSubId_dataConnectionState_invalid() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(45, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+
+ job.cancel()
+ }
+
+ @Test
fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) {
var latest: MobileConnectionModel? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index ccf378a..9312643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -128,6 +128,11 @@
@Test
fun refreshUsers() = runSelfCancelingTest {
+ val mainUserId = 10
+ val mainUser = mock(UserHandle::class.java)
+ whenever(manager.mainUser).thenReturn(mainUser)
+ whenever(mainUser.identifier).thenReturn(mainUserId)
+
underTest = create(this)
val initialExpectedValue =
setUpUsers(
@@ -166,6 +171,7 @@
assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
assertThat(selectedUserInfo?.isGuest).isTrue()
assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+ assertThat(underTest.mainUserId).isEqualTo(mainUserId)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index fb781e8..cc23485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -204,10 +204,12 @@
}
@Test
- fun `exit - last non-guest was removed - returns to system`() =
+ fun `exit - last non-guest was removed - returns to main user`() =
runBlocking(IMMEDIATE) {
val removedUserId = 310
+ val mainUserId = 10
repository.lastSelectedNonGuestUserId = removedUserId
+ repository.mainUserId = mainUserId
repository.setSelectedUserInfo(GUEST_USER_INFO)
underTest.exit(
@@ -221,7 +223,7 @@
verify(manager, never()).markGuestForDeletion(anyInt())
verify(manager, never()).removeUser(anyInt())
- verify(switchUser).invoke(UserHandle.USER_SYSTEM)
+ verify(switchUser).invoke(mainUserId)
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1a8e244..53bb340 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -28,6 +28,10 @@
import kotlinx.coroutines.yield
class FakeUserRepository : UserRepository {
+ companion object {
+ // User id to represent a non system (human) user id. We presume this is the main user.
+ private const val MAIN_USER_ID = 10
+ }
private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
@@ -43,7 +47,8 @@
override val userSwitchingInProgress: Flow<Boolean>
get() = _userSwitchingInProgress
- override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+ override var mainUserId: Int = MAIN_USER_ID
+ override var lastSelectedNonGuestUserId: Int = mainUserId
private var _isGuestUserAutoCreated: Boolean = false
override val isGuestUserAutoCreated: Boolean
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
new file mode 100644
index 0000000..ed45e7b
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates the feature flags for always on magnification. {@see DeviceConfig}
+ *
+ * @hide
+ */
+public class AlwaysOnMagnificationFeatureFlag {
+
+ private static final String NAMESPACE = DeviceConfig.NAMESPACE_WINDOW_MANAGER;
+ private static final String FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION =
+ "AlwaysOnMagnifier__enable_always_on_magnifier";
+
+ private AlwaysOnMagnificationFeatureFlag() {}
+
+ /** Returns true if the feature flag is enabled for always on magnification */
+ public static boolean isAlwaysOnMagnificationEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE,
+ FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION,
+ /* defaultValue= */ false);
+ }
+
+ /** Sets the feature flag. Only used for testing; requires shell permissions. */
+ @VisibleForTesting
+ public static boolean setAlwaysOnMagnificationEnabled(boolean isEnabled) {
+ return DeviceConfig.setProperty(
+ NAMESPACE,
+ FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION,
+ Boolean.toString(isEnabled),
+ /* makeDefault= */ false);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 37069dc..595cdec 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -52,6 +52,7 @@
import android.view.animation.DecelerateInterpolator;
import com.android.internal.R;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -1157,11 +1158,14 @@
}
/**
- * Persists the default display magnification scale to the current user's settings.
+ * Persists the default display magnification scale to the current user's settings
+ * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
+ * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
+ * will be no obvious magnification effect.
*/
public void persistScale(int displayId) {
final float scale = getScale(Display.DEFAULT_DISPLAY);
- if (scale < 2.0f) {
+ if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
mScaleProvider.putScale(scale, displayId);
@@ -1176,7 +1180,8 @@
*/
public float getPersistedScale(int displayId) {
return MathUtils.constrain(mScaleProvider.getScale(displayId),
- 2.0f, MagnificationScaleProvider.MAX_SCALE);
+ MagnificationConstants.PERSISTED_SCALE_MIN_VALUE,
+ MagnificationScaleProvider.MAX_SCALE);
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 6bf37a1..9fc9d57 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -992,9 +992,8 @@
mFullScreenMagnificationController.getPersistedScale(mDisplayId),
MIN_SCALE, MAX_SCALE);
- final float scale = MathUtils.constrain(Math.max(currentScale + 1.0f, persistedScale),
- MIN_SCALE, MAX_SCALE);
-
+ final boolean isActivated = mFullScreenMagnificationController.isActivated(mDisplayId);
+ final float scale = isActivated ? (currentScale + 1.0f) : persistedScale;
zoomToScale(scale, centerX, centerY);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 2d5f894..d9391f4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -46,6 +46,7 @@
import android.view.accessibility.IWindowMagnificationConnectionCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -716,17 +717,20 @@
*/
float getPersistedScale(int displayId) {
return MathUtils.constrain(mScaleProvider.getScale(displayId),
- 2.0f, MagnificationScaleProvider.MAX_SCALE);
+ MagnificationConstants.PERSISTED_SCALE_MIN_VALUE,
+ MagnificationScaleProvider.MAX_SCALE);
}
/**
* Persists the default display magnification scale to the current user's settings
- * <strong>if scale is >= 2.0</strong>. Only the
- * value of the default display is persisted in user's settings.
+ * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
+ * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
+ * will be no obvious magnification effect.
+ * Only the value of the default display is persisted in user's settings.
*/
void persistScale(int displayId) {
float scale = getScale(displayId);
- if (scale < 2.0f) {
+ if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
mScaleProvider.putScale(scale, displayId);
diff --git a/services/api/current.txt b/services/api/current.txt
index 70ee3b8..a4deed3 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -227,8 +227,9 @@
package com.android.server.security {
- public final class FileIntegrityLocal {
- method public static void setUpFsVerity(@NonNull String) throws java.io.IOException;
+ public final class FileIntegrity {
+ method public static void setUpFsVerity(@NonNull java.io.File) throws java.io.IOException;
+ method public static void setUpFsVerity(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
}
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 998c9c2..7261709 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -551,7 +551,7 @@
mPackageManagerBinder = AppGlobals.getPackageManager();
mActivityManager = ActivityManager.getService();
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId,
+ mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId, mContext,
BackupDestination.CLOUD);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -4118,13 +4118,14 @@
public BackupEligibilityRules getEligibilityRulesForOperation(
@BackupDestination int backupDestination) {
- return getEligibilityRules(mPackageManager, mUserId, backupDestination);
+ return getEligibilityRules(mPackageManager, mUserId, mContext, backupDestination);
}
private static BackupEligibilityRules getEligibilityRules(PackageManager packageManager,
- int userId, @BackupDestination int backupDestination) {
+ int userId, Context context, @BackupDestination int backupDestination) {
return new BackupEligibilityRules(packageManager,
- LocalServices.getService(PackageManagerInternal.class), userId, backupDestination);
+ LocalServices.getService(PackageManagerInternal.class), userId, context,
+ backupDestination);
}
/** Prints service state for 'dumpsys backup'. */
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 515a172..2374dee 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -112,7 +112,9 @@
BackupEligibilityRules eligibilityRules = new BackupEligibilityRules(
mBackupManagerService.getPackageManager(),
LocalServices.getService(PackageManagerInternal.class),
- mBackupManagerService.getUserId(), BackupDestination.ADB_BACKUP);
+ mBackupManagerService.getUserId(),
+ mBackupManagerService.getContext(),
+ BackupDestination.ADB_BACKUP);
FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
mOperationStorage, null, mObserver, null, null,
true, 0 /*unused*/, true, eligibilityRules);
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index 2ee9174..7c47f1e 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -31,6 +31,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.Overridable;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -39,10 +40,12 @@
import android.content.pm.SigningInfo;
import android.os.Build;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.server.backup.SetUtils;
import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
@@ -56,13 +59,26 @@
*/
public class BackupEligibilityRules {
private static final boolean DEBUG = false;
- // List of system packages that are eligible for backup in non-system users.
- private static final Set<String> systemPackagesAllowedForAllUsers = Sets.newArraySet(
- PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME, WALLPAPER_PACKAGE, SETTINGS_PACKAGE);
+
+ /**
+ * List of system packages that are eligible for backup in "profile" users (such as work
+ * profile). See {@link UserManager#isProfile()}. This is a subset of {@link
+ * #systemPackagesAllowedForNonSystemUsers}
+ */
+ private static final Set<String> systemPackagesAllowedForProfileUser =
+ Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME);
+
+ /**
+ * List of system packages that are eligible for backup in non-system users.
+ */
+ private static final Set<String> systemPackagesAllowedForNonSystemUsers = SetUtils.union(
+ systemPackagesAllowedForProfileUser,
+ Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final int mUserId;
+ private boolean mIsProfileUser = false;
@BackupDestination private final int mBackupDestination;
/**
@@ -85,19 +101,23 @@
public static BackupEligibilityRules forBackup(PackageManager packageManager,
PackageManagerInternal packageManagerInternal,
- int userId) {
- return new BackupEligibilityRules(packageManager, packageManagerInternal, userId,
+ int userId,
+ Context context) {
+ return new BackupEligibilityRules(packageManager, packageManagerInternal, userId, context,
BackupDestination.CLOUD);
}
public BackupEligibilityRules(PackageManager packageManager,
PackageManagerInternal packageManagerInternal,
int userId,
+ Context context,
@BackupDestination int backupDestination) {
mPackageManager = packageManager;
mPackageManagerInternal = packageManagerInternal;
mUserId = userId;
mBackupDestination = backupDestination;
+ UserManager userManager = context.getSystemService(UserManager.class);
+ mIsProfileUser = userManager.isProfile();
}
/**
@@ -125,11 +145,17 @@
// 2. they run as a system-level uid
if (UserHandle.isCore(app.uid)) {
- // and the backup is happening for a non-system user on a package that is not explicitly
- // allowed.
- if (mUserId != UserHandle.USER_SYSTEM
- && !systemPackagesAllowedForAllUsers.contains(app.packageName)) {
- return false;
+ // and the backup is happening for a non-system user or profile on a package that is
+ // not explicitly allowed.
+ if (mUserId != UserHandle.USER_SYSTEM) {
+ if (mIsProfileUser && !systemPackagesAllowedForProfileUser.contains(
+ app.packageName)) {
+ return false;
+ }
+ if (!mIsProfileUser && !systemPackagesAllowedForNonSystemUsers.contains(
+ app.packageName)) {
+ return false;
+ }
}
// or do not supply their own backup agent
diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
index 8e8bac4..0accb9f 100644
--- a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
@@ -160,7 +160,8 @@
PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
BackupEligibilityRules eligibilityRules =
- BackupEligibilityRules.forBackup(packageManager, pmi, userId);
+ BackupEligibilityRules.forBackup(packageManager, pmi, userId,
+ context);
if (eligibilityRules.signaturesMatch(sigs, pkg)) {
// If this is a system-uid app without a declared backup agent,
// don't restore any of the file data.
diff --git a/services/backup/java/com/android/server/backup/utils/SetUtils.java b/services/backup/java/com/android/server/backup/utils/SetUtils.java
new file mode 100644
index 0000000..ecd628f
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/SetUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.utils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class containing common operation on {@link java.util.Set}.
+ */
+public final class SetUtils {
+ // Statics only
+ private SetUtils() {}
+
+ /**
+ * Returns union of two sets.
+ */
+ public static <T> Set<T> union(Set<T> set1, Set<T> set2) {
+ Set<T> unionSet = new HashSet<>(set1);
+ unionSet.addAll(set2);
+ return unionSet;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 6963248..71ca8ca 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -47,6 +47,7 @@
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManagerMonitor;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -384,13 +385,14 @@
* @param info - file metadata.
* @param signatures - array of signatures parsed from backup file.
* @param userId - ID of the user for which restore is performed.
+ * @param context - Context instance.
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures,
- PackageManagerInternal pmi, int userId) {
+ PackageManagerInternal pmi, int userId, Context context) {
return chooseRestorePolicy(packageManager, allowApks, info, signatures, pmi, userId,
- BackupEligibilityRules.forBackup(packageManager, pmi, userId));
+ BackupEligibilityRules.forBackup(packageManager, pmi, userId, context));
}
/**
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index b6a2a0e..fc81675 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -34,6 +34,7 @@
import android.content.IntentFilter;
import android.content.pm.ApexStagedEvent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Checksum;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.IPackageManagerNative;
import android.content.pm.IStagedApexObserver;
@@ -84,6 +85,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.pm.ApexManager;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;
import libcore.util.HexEncoding;
@@ -120,15 +122,6 @@
static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
- @VisibleForTesting
- static final String BUNDLE_PACKAGE_NAME = "package-name";
- @VisibleForTesting
- static final String BUNDLE_PACKAGE_IS_APEX = "package-is-apex";
- @VisibleForTesting
- static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo";
- @VisibleForTesting
- static final String BUNDLE_CONTENT_DIGEST = "content-digest";
-
static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
// used for indicating any type of error during MBA measurement
@@ -170,29 +163,6 @@
return mVbmetaDigest;
}
- @Override
- public List getApexInfo() {
- List<Bundle> results = new ArrayList<>();
-
- for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
- PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
- packageInfo.packageName);
- if (packageState == null) {
- Slog.w(TAG, "Package state is unavailable, ignoring the package "
- + packageInfo.packageName);
- continue;
- }
- Bundle apexMeasurement = measurePackage(packageState);
- if (apexMeasurement == null) {
- Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
- continue;
- }
- results.add(apexMeasurement);
- }
-
- return results;
- }
-
/**
* A helper function to compute the SHA256 digest of APK package signer.
* @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
@@ -217,58 +187,102 @@
return resultList.toArray(new String[1]);
}
- /**
- * Perform basic measurement (i.e. content digest) on a given package.
+ /*
+ * Perform basic measurement (i.e. content digest) on a given app, including the split APKs.
+ *
* @param packageState The package to be measured.
- * @return a {@link android.os.Bundle} that packs the measurement result with the following
- * keys: {@link #BUNDLE_PACKAGE_NAME},
- * {@link #BUNDLE_PACKAGE_IS_APEX}
- * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
- * {@link #BUNDLE_CONTENT_DIGEST}
+ * @param mbaStatus Assign this value of MBA status to the returned elements.
+ * @return a @{@code List<IBinaryTransparencyService.AppInfo>}
*/
- private @Nullable Bundle measurePackage(PackageState packageState) {
- Bundle result = new Bundle();
-
+ private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ PackageState packageState, int mbaStatus) {
// compute content digest
if (DEBUG) {
Slog.d(TAG, "Computing content digest for " + packageState.getPackageName() + " at "
+ packageState.getPath());
}
+
+ var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
+
+ // Same attributes across base and splits.
+ String packageName = packageState.getPackageName();
+ long versionCode = packageState.getVersionCode();
+ String[] signerDigests =
+ computePackageSignerSha256Digests(packageState.getSigningInfo());
+
AndroidPackage pkg = packageState.getAndroidPackage();
- if (pkg == null) {
- Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
- return null;
+ for (AndroidPackageSplit split : pkg.getSplits()) {
+ var appInfo = new IBinaryTransparencyService.AppInfo();
+ appInfo.packageName = packageName;
+ appInfo.longVersion = versionCode;
+ appInfo.splitName = split.getName(); // base's split name is null
+ // Signer digests are consistent between splits, guaranteed by Package Manager.
+ appInfo.signerDigests = signerDigests;
+ appInfo.mbaStatus = mbaStatus;
+
+ // Only digest and split name are different between splits.
+ Checksum checksum = measureApk(split.getPath());
+ appInfo.digest = checksum.getValue();
+ appInfo.digestAlgorithm = checksum.getType();
+
+ results.add(appInfo);
}
- Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath());
- result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName());
+
+ // InstallSourceInfo is only available per package name, so store it only on the base
+ // APK. It's not current currently available in PackageState (there's a TODO), to we
+ // need to extract manually with another call.
+ //
+ // Base APK is already the 0-th split from getSplits() and can't be null.
+ AppInfo base = results.get(0);
+ InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+ packageState.getPackageName());
+ if (installSourceInfo != null) {
+ base.initiator = installSourceInfo.getInitiatingPackageName();
+ SigningInfo initiatorSignerInfo =
+ installSourceInfo.getInitiatingPackageSigningInfo();
+ if (initiatorSignerInfo != null) {
+ base.initiatorSignerDigests =
+ computePackageSignerSha256Digests(initiatorSignerInfo);
+ }
+ base.installer = installSourceInfo.getInstallingPackageName();
+ base.originator = installSourceInfo.getOriginatingPackageName();
+ }
+
+ return results;
+ }
+
+ /**
+ * Perform basic measurement (i.e. content digest) on a given APK.
+ *
+ * @param apkPath The APK (or APEX, since it's also an APK) file to be measured.
+ * @return a {@link android.content.pm.Checksum} with preferred digest algorithm type and
+ * the checksum.
+ */
+ private @Nullable Checksum measureApk(@NonNull String apkPath) {
+ // compute content digest
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(apkPath);
if (contentDigests == null) {
- Slog.d(TAG, "Failed to compute content digest for " + pkg.getBaseApkPath());
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
- return result;
+ Slog.d(TAG, "Failed to compute content digest for " + apkPath);
+ return new Checksum(0, new byte[] { -1 });
}
// in this iteration, we'll be supporting only 2 types of digests:
// CHUNKED_SHA256 and CHUNKED_SHA512.
// And only one of them will be available per package.
if (contentDigests.containsKey(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
- Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ return new Checksum(
+ Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+ contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256));
} else if (contentDigests.containsKey(
ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
- Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ return new Checksum(
+ Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
+ contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512));
} else {
// TODO(b/259423111): considering putting the raw values for the algorithm & digest
// into the bundle to track potential other digest algorithms that may be in use
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+ return new Checksum(0, new byte[] { -1 });
}
- result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex());
-
- return result;
}
@@ -330,7 +344,7 @@
if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
// lastly measure all newly installed MBAs
List<IBinaryTransparencyService.AppInfo> allMbaInfo =
- collectAllMbaInfo(packagesMeasured);
+ collectAllSilentInstalledMbaInfo(packagesMeasured);
for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
packagesMeasured.putBoolean(appInfo.packageName, true);
writeAppInfoToLog(appInfo);
@@ -356,18 +370,22 @@
continue;
}
- Bundle apexMeasurement = measurePackage(packageState);
- if (apexMeasurement == null) {
- Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
+ AndroidPackage pkg = packageState.getAndroidPackage();
+ if (pkg == null) {
+ Slog.w(TAG, "Skipping the missing APK in " + pkg.getPath());
+ continue;
+ }
+ Checksum apexChecksum = measureApk(pkg.getPath());
+ if (apexChecksum == null) {
+ Slog.w(TAG, "Skipping the missing APEX in " + pkg.getPath());
continue;
}
var apexInfo = new IBinaryTransparencyService.ApexInfo();
apexInfo.packageName = packageState.getPackageName();
apexInfo.longVersion = packageState.getVersionCode();
- apexInfo.digest = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
- apexInfo.digestAlgorithm =
- apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
+ apexInfo.digest = apexChecksum.getValue();
+ apexInfo.digestAlgorithm = apexChecksum.getType();
apexInfo.signerDigests =
computePackageSignerSha256Digests(packageState.getSigningInfo());
@@ -398,28 +416,16 @@
Slog.d(TAG, "Preload " + packageState.getPackageName() + " at "
+ packageState.getPath() + " has likely been updated.");
- Bundle packageMeasurement = measurePackage(packageState);
- if (packageMeasurement == null) {
- Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
- return;
- }
-
- var appInfo = new IBinaryTransparencyService.AppInfo();
- appInfo.packageName = packageState.getPackageName();
- appInfo.longVersion = packageState.getVersionCode();
- appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
- appInfo.digestAlgorithm =
- packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
- appInfo.signerDigests =
- computePackageSignerSha256Digests(packageState.getSigningInfo());
- appInfo.mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
-
- results.add(appInfo);
+ List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
+ packageState, MBA_STATUS_UPDATED_PRELOAD);
+ results.addAll(resultsForApp);
});
return results;
}
- public List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(Bundle packagesToSkip) {
+ @Override
+ public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
+ Bundle packagesToSkip) {
var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
if (packagesToSkip.containsKey(packageInfo.packageName)) {
@@ -433,42 +439,9 @@
continue;
}
- Bundle packageMeasurement = measurePackage(packageState);
- if (packageMeasurement == null) {
- Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
- continue;
- }
- if (DEBUG) {
- Slog.d(TAG,
- "Extracting InstallSourceInfo for " + packageState.getPackageName());
- }
- var appInfo = new IBinaryTransparencyService.AppInfo();
- appInfo.packageName = packageState.getPackageName();
- appInfo.longVersion = packageState.getVersionCode();
- appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
- appInfo.digestAlgorithm =
- packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
- appInfo.signerDigests =
- computePackageSignerSha256Digests(packageState.getSigningInfo());
- appInfo.mbaStatus = MBA_STATUS_NEW_INSTALL;
-
- // Install source isn't currently available in PackageState (there's a TODO).
- // Extract manually with another call.
- InstallSourceInfo installSourceInfo = getInstallSourceInfo(
- packageState.getPackageName());
- if (installSourceInfo != null) {
- appInfo.initiator = installSourceInfo.getInitiatingPackageName();
- SigningInfo initiatorSignerInfo =
- installSourceInfo.getInitiatingPackageSigningInfo();
- if (initiatorSignerInfo != null) {
- appInfo.initiatorSignerDigests =
- computePackageSignerSha256Digests(initiatorSignerInfo);
- }
- appInfo.installer = installSourceInfo.getInstallingPackageName();
- appInfo.originator = installSourceInfo.getOriginatingPackageName();
- }
-
- results.add(appInfo);
+ List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
+ packageState, MBA_STATUS_NEW_INSTALL);
+ results.addAll(resultsForApp);
}
return results;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ce6a6c8..56d0b59 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -20,6 +20,9 @@
import static android.Manifest.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
@@ -7395,6 +7398,12 @@
int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,
callingUid, callingPackage, r, backgroundStartPrivileges);
+ // If an app (App 1) is bound by another app (App 2) that could start an FGS, then App 1
+ // is also allowed to start an FGS. We check all the binding
+ // in canBindingClientStartFgsLocked() to do this check.
+ // (Note we won't check more than 1 level of binding.)
+ // [bookmark: 61867f60-007c-408c-a2c4-e19e96056135] -- this code is referred to from
+ // OomAdjuster.
String bindFromPackage = null;
if (ret == REASON_DENIED) {
bindFromPackage = canBindingClientStartFgsLocked(callingUid);
@@ -7410,10 +7419,13 @@
.getTargetSdkVersion(callingPackage);
} catch (PackageManager.NameNotFoundException ignored) {
}
+ final boolean uidBfsl = (mAm.getUidProcessCapabilityLocked(callingUid)
+ & PROCESS_CAPABILITY_BFSL) != 0;
final String debugInfo =
"[callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
+ "; uidState: " + ProcessList.makeProcStateString(uidState)
+ + "; uidBFSL: " + (uidBfsl ? "[BFSL]" : "n/a")
+ "; intent: " + intent
+ "; code:" + reasonCodeToString(ret)
+ "; tempAllowListReason:<"
@@ -7452,11 +7464,15 @@
}
if (ret == REASON_DENIED) {
+ final boolean uidBfsl =
+ (mAm.getUidProcessCapabilityLocked(callingUid) & PROCESS_CAPABILITY_BFSL) != 0;
final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
if (app.uid == callingUid) {
final ProcessStateRecord state = app.mState;
- if (state.isAllowedStartFgs()) { // Procstate <= BFGS?
- return getReasonCodeFromProcState(state.getCurProcState());
+ final int procstate = state.getCurProcState();
+ if ((procstate <= PROCESS_STATE_BOUND_TOP)
+ || (uidBfsl && (procstate <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE))) {
+ return getReasonCodeFromProcState(procstate);
} else {
final ActiveInstrumentation instr = app.getActiveInstrumentation();
if (instr != null
@@ -7684,6 +7700,8 @@
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
+
+ // TODO(short-service): Log BFSL too.
FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
r.appInfo.uid,
r.shortInstanceName,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b272502..aad49c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5874,6 +5874,11 @@
return mProcessList.getUidProcStateLOSP(uid);
}
+ @GuardedBy("this")
+ int getUidProcessCapabilityLocked(int uid) {
+ return mProcessList.getUidProcessCapabilityLOSP(uid);
+ }
+
// =========================================================
// PROCESS INFO
// =========================================================
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 3ab1cd7..f855c7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,6 +16,10 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NETWORK;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM;
@@ -1823,16 +1827,20 @@
final InputStream mInput;
final int mUid;
+ final int mMask;
+
static final int STATE_NORMAL = 0;
int mState;
- MyUidObserver(ActivityManagerService service, PrintWriter pw, InputStream input, int uid) {
+ MyUidObserver(ActivityManagerService service, PrintWriter pw, InputStream input, int uid,
+ int mask) {
mInterface = service;
mInternal = service;
mPw = pw;
mInput = input;
mUid = uid;
+ mMask = mask;
}
@Override
@@ -1847,7 +1855,7 @@
mPw.print(" seq ");
mPw.print(procStateSeq);
mPw.print(" capability ");
- mPw.println(capability);
+ mPw.println(capability & mMask);
mPw.flush();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
@@ -1998,9 +2006,19 @@
int runWatchUids(PrintWriter pw) throws RemoteException {
String opt;
int uid = -1;
+
+ // Because a lot of CTS won't ignore capabilities newly added, we report
+ // only the following capabilities -- the ones we had on Android T -- by default.
+ int mask = PROCESS_CAPABILITY_FOREGROUND_LOCATION
+ | PROCESS_CAPABILITY_FOREGROUND_CAMERA
+ | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+ | PROCESS_CAPABILITY_NETWORK;
+
while ((opt=getNextOption()) != null) {
if (opt.equals("--oom")) {
uid = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--mask")) {
+ mask = Integer.parseInt(getNextArgRequired());
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -2008,7 +2026,7 @@
}
}
- MyUidObserver controller = new MyUidObserver(mInternal, pw, getRawInputStream(), uid);
+ MyUidObserver controller = new MyUidObserver(mInternal, pw, getRawInputStream(), uid, mask);
controller.run();
return 0;
}
@@ -4079,9 +4097,14 @@
pw.println(" -p: only show events related to a specific process / package");
pw.println(" -s: simple mode, only show a summary line for each event");
pw.println(" -c: assume the input is always [c]ontinue");
- pw.println(" watch-uids [--oom <uid>]");
+ pw.println(" watch-uids [--oom <uid>] [--mask <capabilities integer>]");
pw.println(" Start watching for and reporting uid state changes.");
pw.println(" --oom: specify a uid for which to report detailed change messages.");
+ pw.println(" --mask: Specify PROCESS_CAPABILITY_XXX mask to report. ");
+ pw.println(" By default, it only reports FOREGROUND_LOCATION (1)");
+ pw.println(" FOREGROUND_CAMERA (2), FOREGROUND_MICROPHONE (4)");
+ pw.println(" and NETWORK (8). New capabilities added on or after");
+ pw.println(" Android UDC will not be reported by default.");
pw.println(" hang [--allow-restart]");
pw.println(" Hang the system.");
pw.println(" --allow-restart: allow watchdog to perform normal system restart");
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 3643db0..8591973 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
@@ -1706,7 +1707,7 @@
state.setCurRawAdj(state.getMaxAdj());
state.setHasForegroundActivities(false);
state.setCurrentSchedulingGroup(SCHED_GROUP_DEFAULT);
- state.setCurCapability(PROCESS_CAPABILITY_ALL);
+ state.setCurCapability(PROCESS_CAPABILITY_ALL); // BFSL allowed
state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
// System processes can do UI, and when they do we want to have
// them trim their memory after the user leaves the UI. To
@@ -1788,6 +1789,7 @@
schedGroup = SCHED_GROUP_DEFAULT;
state.setAdjType("instrumentation");
procState = PROCESS_STATE_FOREGROUND_SERVICE;
+ capability |= PROCESS_CAPABILITY_BFSL;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
}
@@ -1880,37 +1882,28 @@
adjType = "fg-service";
newAdj = PERCEPTIBLE_APP_ADJ;
newProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+ capabilityFromFGS |= PROCESS_CAPABILITY_BFSL;
+
+ } else if (hasShortForegroundServices) {
+
+ // For short FGS.
+ adjType = "fg-service-short";
+
+ // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
+ // (which uses MEDIUM_APP_ADJ + 1)
+ // from short-FGS.
+ // (We use +1 and +2, not +0 and +1, to be consistent with the following
+ // RECENT_FOREGROUND_APP_ADJ tweak)
+ newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
+
+ // We give the FGS procstate, but not PROCESS_CAPABILITY_BFSL, so
+ // short-fgs can't start FGS from the background.
+ newProcState = PROCESS_STATE_FOREGROUND_SERVICE;
} else if (state.hasOverlayUi()) {
adjType = "has-overlay-ui";
newAdj = PERCEPTIBLE_APP_ADJ;
newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-
- } else if (hasForegroundServices) {
- // If we get here, hasNonShortForegroundServices() must be false.
-
- // TODO(short-service): Proactively run OomAjudster when the grace period finish.
- if (!hasShortForegroundServices) {
- // All the short-FGSes within this process are timed out. Don't promote to FGS.
- // TODO(short-service): Should we set some unique oom-adj to make it detectable,
- // in a long trace?
- } else {
- // For short FGS.
- adjType = "fg-service-short";
- // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
- // (which uses MEDIUM_APP_ADJ + 2)
- // from short-FGS.
- // (We use +1 and +2, not +0 and +1, to be consistent with the following
- // RECENT_FOREGROUND_APP_ADJ tweak)
- newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
-
- // Short-FGS gets a below-BFGS procstate, so it can't start another FGS from it.
- newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-
- // Same as EJ, we explicitly grant network access to short FGS,
- // even when battery saver or data saver is enabled.
- capabilityFromFGS |= PROCESS_CAPABILITY_NETWORK;
- }
}
if (adjType != null) {
@@ -2220,6 +2213,11 @@
app.mOptRecord.setShouldNotFreeze(true);
}
+ // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
+ // but, right before actually setting it to the process,
+ // we check the final procstate, and remove it if the procsate is below BFGS.
+ capability |= getBfslCapabilityFromClient(client);
+
if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
capability |= cstate.getCurCapability();
@@ -2540,6 +2538,11 @@
int clientAdj = cstate.getCurRawAdj();
int clientProcState = cstate.getCurRawProcState();
+ // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
+ // but, right before actually setting it to the process,
+ // we check the final procstate, and remove it if the procsate is below BFGS.
+ capability |= getBfslCapabilityFromClient(client);
+
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
// If the other app is cached for any reason, for purposes here
// we are going to consider it empty.
@@ -2718,6 +2721,11 @@
capability |= getDefaultCapability(app, procState);
+ // Procstates below BFGS should never have this capability.
+ if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ capability &= ~PROCESS_CAPABILITY_BFSL;
+ }
+
// Do final modification to adj. Everything we do between here and applying
// the final setAdj must be done in this function, because we will also use
// it when computing the final cached adj later. Note that we don't need to
@@ -2743,9 +2751,9 @@
case PROCESS_STATE_PERSISTENT:
case PROCESS_STATE_PERSISTENT_UI:
case PROCESS_STATE_TOP:
- return PROCESS_CAPABILITY_ALL;
+ return PROCESS_CAPABILITY_ALL; // BFSL allowed
case PROCESS_STATE_BOUND_TOP:
- return PROCESS_CAPABILITY_NETWORK;
+ return PROCESS_CAPABILITY_NETWORK | PROCESS_CAPABILITY_BFSL;
case PROCESS_STATE_FOREGROUND_SERVICE:
if (app.getActiveInstrumentation() != null) {
return PROCESS_CAPABILITY_ALL_IMPLICIT | PROCESS_CAPABILITY_NETWORK ;
@@ -2763,6 +2771,53 @@
}
/**
+ * @return the BFSL capability from a client (of a service binding or provider).
+ */
+ int getBfslCapabilityFromClient(ProcessRecord client) {
+ // Procstates above FGS should always have this flag. We shouldn't need this logic,
+ // but let's do it just in case.
+ if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) {
+ return PROCESS_CAPABILITY_BFSL;
+ }
+ // Otherwise, use the process's cur capability.
+
+ // Note: BFSL is a per-UID check, not per-process, but here, the BFSL capability is still
+ // propagated on a per-process basis.
+ //
+ // For example, consider this case:
+ // - There are App 1 and App 2.
+ // - App 1 has two processes
+ // Proc #1A, procstate BFGS with CAPABILITY_BFSL
+ // Proc #1B, procstate FGS with no CAPABILITY_BFSL (i.e. process has a short FGS)
+ // And this process binds to Proc #2 of App 2.
+ //
+ // (Note because #1A has CAPABILITY_BFSL, App 1's UidRecord has CAPABILITY_BFSL.)
+ //
+ // - App 2 has one process:
+ // Proc #2, procstate FGS due to the above binding, _with no CAPABILITY_BFSL_.
+ //
+ // In this case, #2 will not get CAPABILITY_BFSL because the binding client (#1B)
+ // doesn't have this capability. (Even though App 1's UidRecord has it.)
+ //
+ // This may look weird, because App 2 _is_ still BFSL allowed, because "it's bound by
+ // an app that is BFSL-allowed". (See [bookmark: 61867f60-007c-408c-a2c4-e19e96056135]
+ // in ActiveServices.)
+ //
+ // So why don't we propagate PROCESS_CAPABILITY_BFSL from App 1's UID record?
+ // This is because short-FGS acts like "below BFGS" as far as BFSL is concerned,
+ // similar to how JobScheduler jobs are below BFGS and apps can't start FGS from there.
+ //
+ // If #1B was running a job instead of a short-FGS, then its procstate would be below BFGS.
+ // Then #2's procstate would also be below BFGS. So #2 wouldn't get CAPABILITY_BFSL.
+ // Similarly, if #1B has a short FGS, even though the procstate of #1B and #2 would be FGS,
+ // they both still wouldn't get CAPABILITY_BFSL.
+ //
+ // However, again, because #2 is bound by App 1, which is BFSL-allowed (because of #1A)
+ // App 2 would still BFSL-allowed, due to the aforementioned check in ActiveServices.
+ return client.mState.getCurCapability() & PROCESS_CAPABILITY_BFSL;
+ }
+
+ /**
* Checks if for the given app and client, there's a cycle that should skip over the client
* for now or use partial values to evaluate the effect of the client binding.
* @param app
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 2ad2077..71d5d39 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
@@ -1180,11 +1179,6 @@
}
@GuardedBy("mService")
- boolean isAllowedStartFgs() {
- return mCurProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- }
-
- @GuardedBy("mService")
boolean isBackgroundRestricted() {
return mBackgroundRestricted;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index f22624c..60a7f93 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -80,6 +80,7 @@
@VisibleForTesting
static final String[] sDeviceConfigScopes = new String[] {
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ DeviceConfig.NAMESPACE_CAMERA_NATIVE,
DeviceConfig.NAMESPACE_CONFIGURATION,
DeviceConfig.NAMESPACE_CONNECTIVITY,
DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
@@ -103,6 +104,7 @@
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
+ DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
DeviceConfig.NAMESPACE_HDMI_CONTROL
};
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index b617582..bfc022b 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -443,6 +443,8 @@
sb.append(",");
sb.append(lastNetworkUpdatedProcStateSeq);
sb.append(")}");
+ sb.append(" caps=");
+ ActivityManager.printCapabilitiesSummary(sb, mCurCapability);
return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b2e4740..0a019f3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2246,7 +2246,7 @@
// If there is no challenge set, dismiss the keyguard right away
if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
// Wait until the keyguard is dismissed to unfreeze
- mInjector.dismissKeyguard(runnable, "User Switch");
+ mInjector.dismissKeyguard(runnable);
} else {
runnable.run();
}
@@ -3714,7 +3714,7 @@
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void dismissKeyguard(Runnable runnable, String reason) {
+ protected void dismissKeyguard(Runnable runnable) {
final AtomicBoolean isFirst = new AtomicBoolean(true);
final Runnable runOnce = () -> {
if (isFirst.getAndSet(false)) {
@@ -3738,7 +3738,7 @@
public void onDismissCancelled() throws RemoteException {
mHandler.post(runOnce);
}
- }, reason);
+ }, /* message= */ null);
}
boolean isUsersOnSecondaryDisplaysEnabled() {
diff --git a/services/core/java/com/android/server/backup/SetUtils.java b/services/core/java/com/android/server/backup/SetUtils.java
new file mode 100644
index 0000000..ae70e19
--- /dev/null
+++ b/services/core/java/com/android/server/backup/SetUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class containing common operation on {@link java.util.Set}.
+ */
+public final class SetUtils {
+ // Statics only
+ private SetUtils() {}
+
+ /**
+ * Returns union of two sets.
+ */
+ public static <T> Set<T> union(Set<T> set1, Set<T> set2) {
+ Set<T> unionSet = new HashSet<>(set1);
+ unionSet.addAll(set2);
+ return unionSet;
+ }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index c0ea561..224e34d 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -30,6 +30,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Slog;
import com.google.android.collect.Sets;
@@ -84,30 +85,50 @@
// Use old keys to keep legacy data compatibility and avoid writing two wallpapers
private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY;
- private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
- PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER,
- ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER, SHORTCUT_MANAGER_HELPER
- );
+ /**
+ * Helpers that are enabled for "profile" users (such as work profile). See {@link
+ * UserManager#isProfile()}. This is a subset of {@link #sEligibleHelpersForNonSystemUser}.
+ */
+ private static final Set<String> sEligibleHelpersForProfileUser =
+ Sets.newArraySet(
+ PERMISSION_HELPER,
+ NOTIFICATION_HELPER,
+ SYNC_SETTINGS_HELPER,
+ APP_LOCALES_HELPER);
+
+ /** Helpers that are enabled for full, non-system users. */
+ private static final Set<String> sEligibleHelpersForNonSystemUser =
+ SetUtils.union(sEligibleHelpersForProfileUser,
+ Sets.newArraySet(ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER,
+ SHORTCUT_MANAGER_HELPER));
private int mUserId = UserHandle.USER_SYSTEM;
+ private boolean mIsProfileUser = false;
@Override
public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
super.onCreate(user, backupDestination);
mUserId = user.getIdentifier();
+ if (mUserId != UserHandle.USER_SYSTEM) {
+ Context context = createContextAsUser(user, /* flags= */ 0);
+ UserManager userManager = context.getSystemService(UserManager.class);
+ mIsProfileUser = userManager.isProfile();
+ }
- addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this, mUserId));
- addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId));
- addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
- addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
- addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
- addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper(mUserId));
- addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
- addHelper(SLICES_HELPER, new SliceBackupHelper(this));
- addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
- addHelper(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
- addHelper(APP_GENDER_HELPER, new AppGrammaticalGenderBackupHelper(mUserId));
+ addHelperIfEligibleForUser(
+ SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this, mUserId));
+ addHelperIfEligibleForUser(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId));
+ addHelperIfEligibleForUser(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
+ addHelperIfEligibleForUser(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
+ addHelperIfEligibleForUser(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
+ addHelperIfEligibleForUser(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper(mUserId));
+ addHelperIfEligibleForUser(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
+ addHelperIfEligibleForUser(SLICES_HELPER, new SliceBackupHelper(this));
+ addHelperIfEligibleForUser(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
+ addHelperIfEligibleForUser(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
+ addHelperIfEligibleForUser(APP_GENDER_HELPER,
+ new AppGrammaticalGenderBackupHelper(mUserId));
}
@Override
@@ -131,15 +152,6 @@
super.onRestore(data, appVersionCode, newState);
}
- @Override
- public void addHelper(String keyPrefix, BackupHelper helper) {
- if (mUserId != UserHandle.USER_SYSTEM && !sEligibleForMultiUser.contains(keyPrefix)) {
- return;
- }
-
- super.addHelper(keyPrefix, helper);
- }
-
/**
* Support for 'adb restore' of legacy archives
*/
@@ -190,4 +202,25 @@
}
}
}
+
+ private void addHelperIfEligibleForUser(String keyPrefix, BackupHelper helper) {
+ if (isHelperEligibleForUser(keyPrefix)) {
+ addHelper(keyPrefix, helper);
+ }
+ }
+
+ private boolean isHelperEligibleForUser(String keyPrefix) {
+ // All helpers are eligible for the system user.
+ if (mUserId == UserHandle.USER_SYSTEM) {
+ return true;
+ }
+
+ // Profile users (such as work profile) have their own allow list.
+ if (mIsProfileUser) {
+ return sEligibleHelpersForProfileUser.contains(keyPrefix);
+ }
+
+ // Full, non-system users have their own allow list.
+ return sEligibleHelpersForNonSystemUser.contains(keyPrefix);
+ }
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 15f4d2e..7026529 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,6 +22,7 @@
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.view.DisplayAddress;
import com.android.internal.annotations.VisibleForTesting;
@@ -114,6 +115,7 @@
Slog.i(TAG, "Display layout config not found: " + configFile);
return;
}
+ int leadDisplayId = Display.DEFAULT_DISPLAY;
for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
@@ -124,7 +126,8 @@
d.isDefaultDisplay(),
d.isEnabled(),
mIdProducer,
- d.getBrightnessThrottlingMapId());
+ d.getBrightnessThrottlingMapId(),
+ leadDisplayId);
if (FRONT_STRING.equals(d.getPosition())) {
display.setPosition(POSITION_FRONT);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 70069c6..237e78b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -142,6 +142,7 @@
import com.android.server.UiThread;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.layout.Layout;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.wm.SurfaceAnimationThread;
@@ -1665,12 +1666,37 @@
return;
}
- // TODO (b/265793751): Set this DPC as a follower of the default DPC if needed,
- // clear this DPC's followers if it's not a lead display
+ final int leadDisplayId = display.getLeadDisplayIdLocked();
+ updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
final String uniqueId = device.getUniqueId();
HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId);
- dpc.onDisplayChanged(hbmMetadata);
+ dpc.onDisplayChanged(hbmMetadata, leadDisplayId);
+ }
+ }
+
+ private void updateDisplayPowerControllerLeaderLocked(DisplayPowerControllerInterface dpc,
+ int leadDisplayId) {
+ if (dpc.getLeadDisplayId() == leadDisplayId) {
+ // Lead display hasn't changed, nothing to do.
+ return;
+ }
+
+ // If it has changed, then we need to unregister from the previous leader if there was one.
+ final int prevLeaderId = dpc.getLeadDisplayId();
+ if (prevLeaderId != Layout.NO_LEAD_DISPLAY) {
+ final DisplayPowerControllerInterface prevLeader =
+ mDisplayPowerControllers.get(prevLeaderId);
+ if (prevLeader != null) {
+ prevLeader.removeDisplayBrightnessFollower(dpc);
+ }
+ }
+
+ // And then, if it's following, register it with the new one.
+ if (leadDisplayId != Layout.NO_LEAD_DISPLAY) {
+ final DisplayPowerControllerInterface newLead =
+ mDisplayPowerControllers.get(leadDisplayId);
+ newLead.addDisplayBrightnessFollower(dpc);
}
}
@@ -1734,9 +1760,13 @@
+ display.getDisplayIdLocked());
return;
}
+
+ final int leadDisplayId = display.getLeadDisplayIdLocked();
+ updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
+
final String uniqueId = device.getUniqueId();
HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId);
- dpc.onDisplayChanged(hbmMetadata);
+ dpc.onDisplayChanged(hbmMetadata, leadDisplayId);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 40eec33..1305d63 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -74,6 +75,7 @@
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.layout.Layout;
import com.android.server.display.utils.SensorUtils;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
@@ -195,6 +197,9 @@
// The ID of the LogicalDisplay tied to this DisplayPowerController.
private final int mDisplayId;
+ // The ID of the display which this display follows for brightness purposes.
+ private int mLeadDisplayId = Layout.NO_LEAD_DISPLAY;
+
// The unique ID of the primary display device currently tied to this logical display
private String mUniqueDisplayId;
@@ -509,8 +514,8 @@
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
- private SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
- new SparseArray();
+ private final SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
+ new SparseArray<>();
/**
* Creates the display power controller.
@@ -722,6 +727,11 @@
}
@Override
+ public int getLeadDisplayId() {
+ return mLeadDisplayId;
+ }
+
+ @Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
mHbmController.onAmbientLuxChange(ambientLux);
if (mAutomaticBrightnessController == null || nits < 0) {
@@ -739,24 +749,20 @@
}
@Override
- public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+ public void addDisplayBrightnessFollower(@NonNull DisplayPowerControllerInterface follower) {
synchronized (mLock) {
mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
+ sendUpdatePowerStateLocked();
}
- sendUpdatePowerState();
}
@Override
- public void clearDisplayBrightnessFollowers() {
- SparseArray<DisplayPowerControllerInterface> followers;
+ public void removeDisplayBrightnessFollower(@NonNull DisplayPowerControllerInterface follower) {
synchronized (mLock) {
- followers = mDisplayBrightnessFollowers.clone();
- mDisplayBrightnessFollowers.clear();
- }
- for (int i = 0; i < followers.size(); i++) {
- DisplayPowerControllerInterface follower = followers.valueAt(i);
- follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
- /* ambientLux= */ 0);
+ mDisplayBrightnessFollowers.remove(follower.getDisplayId());
+ mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+ /* ambientLux= */ 0), mClock.uptimeMillis());
}
}
@@ -851,7 +857,8 @@
* Make sure DisplayManagerService.mSyncRoot is held when this is called
*/
@Override
- public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
+ public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
+ mLeadDisplayId = leadDisplayId;
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
@@ -1011,7 +1018,7 @@
}
mBrightnessSettingListener = brightnessValue -> {
Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
};
mBrightnessSetting.registerListener(mBrightnessSettingListener);
@@ -1040,7 +1047,7 @@
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
- mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+ mInteractiveModeBrightnessMapper = mInjector.getInteractiveModeBrightnessMapper(resources,
mDisplayDeviceConfig, mDisplayWhiteBalanceController);
if (isIdleScreenBrightnessEnabled) {
mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
@@ -1065,7 +1072,7 @@
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
@@ -1083,7 +1090,7 @@
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
screenBrighteningThresholds, screenDarkeningThresholds,
screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
screenBrighteningMinThreshold, true);
@@ -1101,7 +1108,7 @@
mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
float[] ambientDarkeningLevelsIdle =
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
@@ -1119,7 +1126,7 @@
mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
float[] screenDarkeningLevelsIdle =
mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
@@ -1155,8 +1162,8 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
- mAutomaticBrightnessController = new AutomaticBrightnessController(this,
- handler.getLooper(), mSensorManager, mLightSensor,
+ mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
+ this, handler.getLooper(), mSensorManager, mLightSensor,
mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
@@ -1257,7 +1264,7 @@
public void onAnimationEnd() {
sendUpdatePowerState();
Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
};
@@ -2701,6 +2708,7 @@
pw.println();
pw.println("Display Power Controller:");
pw.println(" mDisplayId=" + mDisplayId);
+ pw.println(" mLeadDisplayId=" + mLeadDisplayId);
pw.println(" mLightSensor=" + mLightSensor);
pw.println();
@@ -2949,7 +2957,8 @@
msg.what = MSG_STATSD_HBM_BRIGHTNESS;
msg.arg1 = Float.floatToIntBits(brightness);
msg.arg2 = mDisplayStatsId;
- mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()
+ + BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
}
}
}
@@ -3105,7 +3114,7 @@
@Override
public void onScreenOn() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -3113,7 +3122,7 @@
@Override
public void onScreenOff() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -3195,6 +3204,58 @@
FloatProperty<DisplayPowerState> secondProperty) {
return new DualRampAnimator(dps, firstProperty, secondProperty);
}
+
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
+ return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
+ interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin,
+ brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
+ idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
+ userLux, userBrightness);
+ }
+
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return BrightnessMappingStrategy.create(resources,
+ displayDeviceConfig, displayWhiteBalanceController);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
+ potentialOldBrightnessRange);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 6092ad7..82faa12 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -73,6 +73,7 @@
import com.android.server.display.brightness.DisplayBrightnessController;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.layout.Layout;
import com.android.server.display.state.DisplayStateController;
import com.android.server.display.utils.SensorUtils;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -179,6 +180,9 @@
// The ID of the LogicalDisplay tied to this DisplayPowerController2.
private final int mDisplayId;
+ // The ID of the display which this display follows for brightness purposes.
+ private int mLeadDisplayId = Layout.NO_LEAD_DISPLAY;
+
// The unique ID of the primary display device currently tied to this logical display
private String mUniqueDisplayId;
@@ -694,7 +698,8 @@
* Make sure DisplayManagerService.mSyncRoot lock is held when this is called
*/
@Override
- public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
+ public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
+ mLeadDisplayId = leadDisplayId;
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
@@ -850,7 +855,7 @@
BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
};
mDisplayBrightnessController
.registerBrightnessSettingChangeListener(brightnessSettingListener);
@@ -880,7 +885,7 @@
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
- mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+ mInteractiveModeBrightnessMapper = mInjector.getInteractiveModeBrightnessMapper(resources,
mDisplayDeviceConfig, mDisplayWhiteBalanceController);
if (isIdleScreenBrightnessEnabled) {
mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
@@ -905,7 +910,7 @@
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
@@ -923,7 +928,7 @@
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
screenBrighteningThresholds, screenDarkeningThresholds,
screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
screenBrighteningMinThreshold, true);
@@ -941,7 +946,7 @@
mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
float[] ambientDarkeningLevelsIdle =
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
@@ -959,7 +964,7 @@
mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
float[] screenDarkeningLevelsIdle =
mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
@@ -995,8 +1000,8 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
- mAutomaticBrightnessController = new AutomaticBrightnessController(this,
- handler.getLooper(), mSensorManager, mLightSensor,
+ mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
+ this, handler.getLooper(), mSensorManager, mLightSensor,
mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
@@ -1094,7 +1099,7 @@
public void onAnimationEnd() {
sendUpdatePowerState();
Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
};
@@ -2149,6 +2154,11 @@
}
@Override
+ public int getLeadDisplayId() {
+ return mLeadDisplayId;
+ }
+
+ @Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
mHbmController.onAmbientLuxChange(ambientLux);
if (mAutomaticBrightnessController == null || nits < 0) {
@@ -2218,21 +2228,17 @@
public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
synchronized (mLock) {
mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
+ sendUpdatePowerStateLocked();
}
- sendUpdatePowerState();
}
@Override
- public void clearDisplayBrightnessFollowers() {
- SparseArray<DisplayPowerControllerInterface> followers;
+ public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
synchronized (mLock) {
- followers = mDisplayBrightnessFollowers.clone();
- mDisplayBrightnessFollowers.clear();
- }
- for (int i = 0; i < followers.size(); i++) {
- DisplayPowerControllerInterface follower = followers.valueAt(i);
- follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
- /* ambientLux= */ 0);
+ mDisplayBrightnessFollowers.remove(follower.getDisplayId());
+ mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+ /* ambientLux= */ 0), mClock.uptimeMillis());
}
}
@@ -2242,6 +2248,7 @@
pw.println();
pw.println("Display Power Controller:");
pw.println(" mDisplayId=" + mDisplayId);
+ pw.println(" mLeadDisplayId=" + mLeadDisplayId);
pw.println(" mLightSensor=" + mLightSensor);
pw.println();
@@ -2458,7 +2465,8 @@
msg.what = MSG_STATSD_HBM_BRIGHTNESS;
msg.arg1 = Float.floatToIntBits(brightness);
msg.arg2 = mDisplayStatsId;
- mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()
+ + BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
}
}
}
@@ -2589,7 +2597,7 @@
@Override
public void onScreenOn() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -2597,7 +2605,7 @@
@Override
public void onScreenOff() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -2671,6 +2679,58 @@
looper, nudgeUpdatePowerState,
displayId, sensorManager, /* injector= */ null);
}
+
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
+ return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
+ interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin,
+ brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
+ idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
+ userLux, userBrightness);
+ }
+
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return BrightnessMappingStrategy.create(resources,
+ displayDeviceConfig, displayWhiteBalanceController);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
+ potentialOldBrightnessRange);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 4612ec9..0bc8154 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -32,13 +32,18 @@
/**
* Notified when the display is changed.
- * We use this to apply any changes that might be needed
- * when displays get swapped on foldable devices.
- * We also pass the High brightness mode metadata like
- * remaining time and hbm events for the corresponding
- * physical display, to update the values correctly.
+ *
+ * We use this to apply any changes that might be needed when displays get swapped on foldable
+ * devices, when layouts change, etc.
+ *
+ * Must be called while holding the SyncRoot lock.
+ *
+ * @param hbmInfo The high brightness mode metadata, like
+ * remaining time and hbm events, for the corresponding
+ * physical display, to make sure we stay within the safety margins.
+ * @param leadDisplayId The display who is considered our "leader" for things like brightness.
*/
- void onDisplayChanged(HighBrightnessModeMetadata hbmInfo);
+ void onDisplayChanged(HighBrightnessModeMetadata hbmInfo, int leadDisplayId);
/**
* Unregisters all listeners and interrupts all running threads; halting future work.
@@ -169,6 +174,16 @@
int getDisplayId();
/**
+ * Get the ID of the display that is the leader of this DPC.
+ *
+ * Note that this is different than the display associated with the DPC. The leader is another
+ * display which we follow for things like brightness.
+ *
+ * Must be called while holding the SyncRoot lock.
+ */
+ int getLeadDisplayId();
+
+ /**
* Set the brightness to follow if this is an additional display in a set of concurrent
* displays.
* @param leadDisplayBrightness The brightness of the lead display in the set of concurrent
@@ -187,7 +202,8 @@
void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower);
/**
- * Clear all the additional displays following the brightness value of this display.
+ * Removes the given display from the list of brightness followers.
+ * @param follower The DPC to remove from the followers list
*/
- void clearDisplayBrightnessFollowers();
+ void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower);
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 4bb1f0e..6090386 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -77,6 +77,12 @@
private final int mDisplayId;
private final int mLayerStack;
+ // Indicates which display leads this logical display, in terms of brightness or other
+ // properties.
+ // {@link Layout.NO_LEAD_DISPLAY} means that this display is not lead by any others, and could
+ // be a leader itself.
+ private int mLeadDisplayId = Layout.NO_LEAD_DISPLAY;
+
private int mDisplayGroupId = Display.INVALID_DISPLAY_GROUP;
/**
@@ -150,7 +156,7 @@
// Indicates the display is part of a transition from one device-state ({@link
// DeviceStateManager}) to another. Being a "part" of a transition means that either
- // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing.
+ // the {@link mIsEnabled} is changing, or the underlying mPrimaryDisplayDevice is changing.
private boolean mIsInTransition;
// Indicates the position of the display, POSITION_UNKNOWN could mean it hasn't been specified,
@@ -826,6 +832,26 @@
brightnessThrottlingDataId;
}
+ /**
+ * Sets the display of which this display is a follower, regarding brightness or other
+ * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any
+ * others, and has the potential to be a lead display to others.
+ *
+ * A display cannot be a leader or follower of itself, and there cannot be cycles.
+ * A display cannot be both a leader and a follower, ie, there must not be any chains.
+ *
+ * @param displayId logical display id
+ */
+ public void setLeadDisplayLocked(int displayId) {
+ if (mDisplayId != mLeadDisplayId && mDisplayId != displayId) {
+ mLeadDisplayId = displayId;
+ }
+ }
+
+ public int getLeadDisplayIdLocked() {
+ return mLeadDisplayId;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled);
@@ -845,6 +871,7 @@
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
+ pw.println("mLeadDisplayId=" + mLeadDisplayId);
}
@Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index a6f09ad..56c9056 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -18,6 +18,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.display.layout.Layout.NO_LEAD_DISPLAY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -639,7 +641,7 @@
&& !nextDeviceInfo.address.equals(deviceInfo.address)) {
layout.createDisplayLocked(nextDeviceInfo.address,
/* isDefault= */ true, /* isEnabled= */ true, mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null, DEFAULT_DISPLAY);
applyLayoutLocked();
return;
}
@@ -991,6 +993,7 @@
}
newDisplay.setPositionLocked(displayLayout.getPosition());
+ newDisplay.setLeadDisplayLocked(displayLayout.getLeadDisplayId());
setLayoutLimitedRefreshRate(newDisplay, device, displayLayout);
setEnabledLocked(newDisplay, displayLayout.isEnabled());
newDisplay.setBrightnessThrottlingDataIdLocked(
@@ -1076,7 +1079,7 @@
}
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
- mIdProducer, /* brightnessThrottlingMapId= */ null);
+ mIdProducer, /* brightnessThrottlingMapId= */ null, NO_LEAD_DISPLAY);
}
private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 8647b50..59d95a6 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -39,6 +39,10 @@
private static final String TAG = "Layout";
private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+ // Lead display Id is set to this if this is not a follower display, and therefore
+ // has no lead.
+ public static final int NO_LEAD_DISPLAY = -1;
+
private final List<Display> mDisplays = new ArrayList<>(2);
/**
@@ -75,13 +79,16 @@
* @param address Address of the device.
* @param isDefault Indicates if the device is meant to be the default display.
* @param isEnabled Indicates if this display is usable and can be switched on
- * @return The new layout.
+ * @param idProducer Produces the logical display id.
+ * @param brightnessThrottlingMapId Name of which throttling policy should be used.
+ * @param leadDisplayId Display that this one follows (-1 if none).
+ * @return The new Display.
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- DisplayIdProducer idProducer, String brightnessThrottlingMapId) {
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId, int leadDisplayId) {
return createDisplayLocked(address, isDefault, isEnabled, idProducer,
- brightnessThrottlingMapId, POSITION_UNKNOWN);
+ brightnessThrottlingMapId, POSITION_UNKNOWN, leadDisplayId);
}
/**
@@ -90,12 +97,16 @@
* @param address Address of the device.
* @param isDefault Indicates if the device is meant to be the default display.
* @param isEnabled Indicates if this display is usable and can be switched on
+ * @param idProducer Produces the logical display id.
+ * @param brightnessThrottlingMapId Name of which throttling policy should be used.
* @param position Indicates the position this display is facing in this layout.
- * @return The new layout.
+ * @param leadDisplayId Display that this one follows (-1 if none).
+ * @return The new Display.
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- DisplayIdProducer idProducer, String brightnessThrottlingMapId, int position) {
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId, int position,
+ int leadDisplayId) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -113,7 +124,7 @@
// same logical display ID.
final int logicalDisplayId = idProducer.getId(isDefault);
final Display display = new Display(address, logicalDisplayId, isEnabled,
- brightnessThrottlingMapId, position);
+ brightnessThrottlingMapId, position, leadDisplayId);
mDisplays.add(display);
return display;
@@ -221,17 +232,27 @@
@Nullable
private final String mBrightnessThrottlingMapId;
+ // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+ private int mLeadDisplayId;
+
// Refresh rate zone id for specific layout
@Nullable
private String mRefreshRateZoneId;
Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
- String brightnessThrottlingMapId, int position) {
+ String brightnessThrottlingMapId, int position, int leadDisplayId) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
mPosition = position;
mBrightnessThrottlingMapId = brightnessThrottlingMapId;
+
+ if (leadDisplayId == mLogicalDisplayId) {
+ mLeadDisplayId = NO_LEAD_DISPLAY;
+ } else {
+ mLeadDisplayId = leadDisplayId;
+ }
+
}
@Override
@@ -243,6 +264,7 @@
+ ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+ ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+ ", mRefreshRateZoneId: " + mRefreshRateZoneId
+ + ", mLeadDisplayId: " + mLeadDisplayId
+ "}";
}
@@ -260,7 +282,8 @@
&& this.mAddress.equals(otherDisplay.mAddress)
&& Objects.equals(mBrightnessThrottlingMapId,
otherDisplay.mBrightnessThrottlingMapId)
- && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId);
+ && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
+ && this.mLeadDisplayId == otherDisplay.mLeadDisplayId;
}
@Override
@@ -272,6 +295,7 @@
result = 31 * result + mAddress.hashCode();
result = 31 * result + mBrightnessThrottlingMapId.hashCode();
result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
+ result = 31 * result + mLeadDisplayId;
return result;
}
@@ -297,6 +321,10 @@
return mRefreshRateZoneId;
}
+ /**
+ * Sets the position that this display is facing.
+ * @param position the display is facing.
+ */
public void setPosition(int position) {
mPosition = position;
}
@@ -308,8 +336,31 @@
return mBrightnessThrottlingMapId;
}
+ /**
+ *
+ * @return the position that this display is facing.
+ */
public int getPosition() {
return mPosition;
}
+
+ /**
+ * Set the display that this display should follow certain properties of, for example,
+ * brightness
+ * @param displayId of the lead display.
+ */
+ public void setLeadDisplay(int displayId) {
+ if (displayId != mLogicalDisplayId) {
+ mLeadDisplayId = displayId;
+ }
+ }
+
+ /**
+ *
+ * @return logical displayId of the display that this one follows.
+ */
+ public int getLeadDisplayId() {
+ return mLeadDisplayId;
+ }
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e2caeec..172aa20 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -32,7 +32,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.database.ContentObserver;
import android.graphics.PointF;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors;
@@ -78,8 +77,6 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -147,8 +144,6 @@
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml";
- // Feature flag name for the deep press feature
- private static final String DEEP_PRESS_ENABLED = "deep_press_enabled";
// Feature flag name for the strategy to be used in VelocityTracker
private static final String VELOCITYTRACKER_STRATEGY_PROPERTY = "velocitytracker_strategy";
@@ -307,6 +302,9 @@
@GuardedBy("mInputMonitors")
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+ // Watches for settings changes and updates the native side appropriately.
+ private final InputSettingsObserver mSettingsObserver;
+
// Manages Keyboard layouts for Physical keyboards
private final KeyboardLayoutManager mKeyboardLayoutManager;
@@ -428,6 +426,7 @@
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
+ mSettingsObserver = new InputSettingsObserver(mContext, mHandler, mNative);
mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
injector.getLooper());
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
@@ -493,39 +492,7 @@
// Add ourselves to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
- registerMousePointerSpeedSettingObserver();
- registerTouchpadPointerSpeedSettingObserver();
- registerTouchpadNaturalScrollingEnabledObserver();
- registerTouchpadTapToClickEnabledObserver();
- registerTouchpadRightClickZoneEnabledObserver();
- registerShowTouchesSettingObserver();
- registerAccessibilityLargePointerSettingObserver();
- registerLongPressTimeoutObserver();
- registerMaximumObscuringOpacityForTouchSettingObserver();
-
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateMousePointerSpeedFromSettings();
- updateTouchpadPointerSpeedFromSettings();
- updateTouchpadNaturalScrollingEnabledFromSettings();
- updateTouchpadTapToClickEnabledFromSettings();
- updateTouchpadRightClickZoneEnabledFromSettings();
- updateShowTouchesFromSettings();
- updateAccessibilityLargePointerFromSettings();
- updateDeepPressStatusFromSettings("user switched");
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
-
- updateMousePointerSpeedFromSettings();
- updateTouchpadPointerSpeedFromSettings();
- updateTouchpadNaturalScrollingEnabledFromSettings();
- updateTouchpadTapToClickEnabledFromSettings();
- updateTouchpadRightClickZoneEnabledFromSettings();
- updateShowTouchesFromSettings();
- updateAccessibilityLargePointerFromSettings();
- updateDeepPressStatusFromSettings("just booted");
- updateMaximumObscuringOpacityForTouchFromSettings();
+ mSettingsObserver.registerAndUpdate();
}
// TODO(BT) Pass in parameter for bluetooth system
@@ -1349,11 +1316,6 @@
setPointerSpeedUnchecked(speed);
}
- private void updateMousePointerSpeedFromSettings() {
- int speed = getMousePointerSpeedSetting();
- setPointerSpeedUnchecked(speed);
- }
-
private void setPointerSpeedUnchecked(int speed) {
speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
InputManager.MAX_POINTER_SPEED);
@@ -1370,194 +1332,6 @@
properties -> properties.pointerIconVisible = visible);
}
- private void registerMousePointerSpeedSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.POINTER_SPEED), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateMousePointerSpeedFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private int getMousePointerSpeedSetting() {
- int speed = InputManager.DEFAULT_POINTER_SPEED;
- try {
- speed = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.POINTER_SPEED, UserHandle.USER_CURRENT);
- } catch (SettingNotFoundException ignored) {
- }
- return speed;
- }
-
- private void registerTouchpadPointerSpeedSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadPointerSpeedFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadPointerSpeedFromSettings() {
- int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_POINTER_SPEED, InputManager.DEFAULT_POINTER_SPEED,
- UserHandle.USER_CURRENT);
- speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
- InputManager.MAX_POINTER_SPEED);
- mNative.setTouchpadPointerSpeed(speed);
- }
-
- private void registerTouchpadNaturalScrollingEnabledObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadNaturalScrollingEnabledFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadNaturalScrollingEnabledFromSettings() {
- int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_NATURAL_SCROLLING, 0, UserHandle.USER_CURRENT);
- mNative.setTouchpadNaturalScrollingEnabled(setting != 0);
- }
-
- private void registerTouchpadTapToClickEnabledObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_TO_CLICK), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadTapToClickEnabledFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadTapToClickEnabledFromSettings() {
- int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT);
- mNative.setTouchpadTapToClickEnabled(setting != 0);
- }
-
- private void registerTouchpadRightClickZoneEnabledObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadRightClickZoneEnabledFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadRightClickZoneEnabledFromSettings() {
- int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, 0, UserHandle.USER_CURRENT);
- mNative.setTouchpadRightClickZoneEnabled(setting != 0);
- }
-
- private void updateShowTouchesFromSettings() {
- int setting = getShowTouchesSetting(0);
- mNative.setShowTouches(setting != 0);
- }
-
- private void registerShowTouchesSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateShowTouchesFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateAccessibilityLargePointerFromSettings() {
- final int accessibilityConfig = Settings.Secure.getIntForUser(
- mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
- 0, UserHandle.USER_CURRENT);
- PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
- mNative.reloadPointerIcons();
- }
-
- private void registerAccessibilityLargePointerSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateAccessibilityLargePointerFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateDeepPressStatusFromSettings(String reason) {
- // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value
- final int timeout = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
- UserHandle.USER_CURRENT);
- final boolean featureEnabledFlag =
- DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
- DEEP_PRESS_ENABLED, true /* default */);
- final boolean enabled =
- featureEnabledFlag && timeout <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
- Log.i(TAG,
- (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
- + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
- + ", long press timeout = " + timeout);
- mNative.setMotionClassifierEnabled(enabled);
- }
-
- private void registerLongPressTimeoutObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LONG_PRESS_TIMEOUT), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateDeepPressStatusFromSettings("timeout changed");
- }
- }, UserHandle.USER_ALL);
- }
-
- private void registerMaximumObscuringOpacityForTouchSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
- /* notifyForDescendants */ true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateMaximumObscuringOpacityForTouchFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateMaximumObscuringOpacityForTouchFromSettings() {
- InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
- final float opacity = im.getMaximumObscuringOpacityForTouch();
- if (opacity < 0 || opacity > 1) {
- Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
- + ", it should be >= 0 and <= 1, rejecting update.");
- return;
- }
- mNative.setMaximumObscuringOpacityForTouch(opacity);
- }
-
- private int getShowTouchesSetting(int defaultValue) {
- int result = defaultValue;
- try {
- result = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.SHOW_TOUCHES, UserHandle.USER_CURRENT);
- } catch (SettingNotFoundException snfe) {
- }
- return result;
- }
-
/**
* Update the display on which the mouse pointer is shown.
*
@@ -2974,6 +2748,13 @@
return null;
}
+ // Native callback.
+ @SuppressWarnings("unused")
+ private boolean isStylusPointerIconEnabled() {
+ return Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .isStylusPointerIconEnabled();
+ }
+
private static class PointerDisplayIdChangedArgs {
final int mPointerDisplayId;
final float mXPosition;
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
new file mode 100644
index 0000000..8ee3a72
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.PointerIcon;
+import android.view.ViewConfiguration;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/** Observes settings changes and propagates them to the native side. */
+class InputSettingsObserver extends ContentObserver {
+ static final String TAG = "InputManager";
+
+ /** Feature flag name for the deep press feature */
+ private static final String DEEP_PRESS_ENABLED = "deep_press_enabled";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final NativeInputManagerService mNative;
+ private final Map<Uri, Consumer<String /* reason*/>> mObservers;
+
+ InputSettingsObserver(Context context, Handler handler, NativeInputManagerService nativeIms) {
+ super(handler);
+ mContext = context;
+ mHandler = handler;
+ mNative = nativeIms;
+ mObservers = Map.ofEntries(
+ Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
+ (reason) -> updateMousePointerSpeed()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
+ (reason) -> updateTouchpadPointerSpeed()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
+ (reason) -> updateTouchpadNaturalScrollingEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_TO_CLICK),
+ (reason) -> updateTouchpadTapToClickEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
+ (reason) -> updateTouchpadRightClickZoneEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
+ (reason) -> updateShowTouches()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON),
+ (reason) -> updateAccessibilityLargePointer()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.LONG_PRESS_TIMEOUT),
+ (reason) -> updateDeepPressStatus(reason)),
+ Map.entry(
+ Settings.Global.getUriFor(Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
+ (reason) -> updateMaximumObscuringOpacityForTouch()));
+ }
+
+ /**
+ * Registers observers for input-related settings and updates the input subsystem with their
+ * current values.
+ */
+ public void registerAndUpdate() {
+ for (Uri uri : mObservers.keySet()) {
+ mContext.getContentResolver().registerContentObserver(
+ uri, true /* notifyForDescendants */, this, UserHandle.USER_ALL);
+ }
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ for (Consumer<String> observer : mObservers.values()) {
+ observer.accept("user switched");
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+
+ for (Consumer<String> observer : mObservers.values()) {
+ observer.accept("just booted");
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mObservers.get(uri).accept("setting changed");
+ }
+
+ private boolean getBoolean(String settingName) {
+ final int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
+ settingName, 0, UserHandle.USER_CURRENT);
+ return setting != 0;
+ }
+
+ private int getPointerSpeedValue(String settingName) {
+ int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+ settingName, InputManager.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+ return Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
+ InputManager.MAX_POINTER_SPEED);
+ }
+
+ private void updateMousePointerSpeed() {
+ mNative.setPointerSpeed(getPointerSpeedValue(Settings.System.POINTER_SPEED));
+ }
+
+ private void updateTouchpadPointerSpeed() {
+ mNative.setTouchpadPointerSpeed(
+ getPointerSpeedValue(Settings.System.TOUCHPAD_POINTER_SPEED));
+ }
+
+ private void updateTouchpadNaturalScrollingEnabled() {
+ mNative.setTouchpadNaturalScrollingEnabled(
+ getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING));
+ }
+
+ private void updateTouchpadTapToClickEnabled() {
+ mNative.setTouchpadTapToClickEnabled(getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK));
+ }
+
+ private void updateTouchpadRightClickZoneEnabled() {
+ mNative.setTouchpadRightClickZoneEnabled(
+ getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE));
+ }
+
+ private void updateShowTouches() {
+ mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES));
+ }
+
+ private void updateAccessibilityLargePointer() {
+ final int accessibilityConfig = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
+ 0, UserHandle.USER_CURRENT);
+ PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
+ mNative.reloadPointerIcons();
+ }
+
+ private void updateDeepPressStatus(String reason) {
+ // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value
+ final int timeout = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
+ UserHandle.USER_CURRENT);
+ final boolean featureEnabledFlag =
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
+ DEEP_PRESS_ENABLED, true /* default */);
+ final boolean enabled =
+ featureEnabledFlag && timeout <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+ Log.i(TAG,
+ (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
+ + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
+ + ", long press timeout = " + timeout);
+ mNative.setMotionClassifierEnabled(enabled);
+ }
+
+ private void updateMaximumObscuringOpacityForTouch() {
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ final float opacity = im.getMaximumObscuringOpacityForTouch();
+ if (opacity < 0 || opacity > 1) {
+ Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
+ + ", it should be >= 0 and <= 1, rejecting update.");
+ return;
+ }
+ mNative.setMaximumObscuringOpacityForTouch(opacity);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index cd4a8f3..0c99e86 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -29,6 +29,7 @@
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -57,6 +58,8 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteLockscreenValidationResult;
+import android.app.StartLockscreenValidationRequest;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
@@ -70,6 +73,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.IAuthSecret;
@@ -116,7 +120,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
-import android.util.FeatureFlagUtils;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -219,6 +222,8 @@
private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+ private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32;
+
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
// Do not call into ActivityManager while holding mSpManager lock.
private final Object mSeparateChallengeLock = new Object();
@@ -267,6 +272,13 @@
@VisibleForTesting
protected boolean mHasSecureLockScreen;
+ @VisibleForTesting
+ protected final Object mHeadlessAuthSecretLock = new Object();
+
+ @VisibleForTesting
+ @GuardedBy("mHeadlessAuthSecretLock")
+ protected byte[] mAuthSecret;
+
protected IGateKeeperService mGateKeeperService;
protected IAuthSecret mAuthSecretService;
@@ -563,6 +575,15 @@
java.security.KeyStore ks) {
return new ManagedProfilePasswordCache(ks, getUserManager());
}
+
+ public boolean isHeadlessSystemUserMode() {
+ return UserManager.isHeadlessSystemUserMode();
+ }
+
+ public boolean isMainUserPermanentAdmin() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_isMainUserPermanentAdmin);
+ }
}
public LockSettingsService(Context context) {
@@ -1697,7 +1718,7 @@
throw new IllegalStateException("password change failed");
}
- onSyntheticPasswordKnown(userId, sp);
+ onSyntheticPasswordUnlocked(userId, sp);
setLockCredentialWithSpLocked(credential, sp, userId);
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
@@ -2009,7 +2030,7 @@
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- onSyntheticPasswordKnown(userId, result.syntheticPassword);
+ onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
unlockUserKey(userId, result.syntheticPassword);
}
}
@@ -2533,23 +2554,18 @@
/**
* Starts a session to verify lock screen credentials provided by a remote device.
*/
- public void startRemoteLockscreenValidation() {
- if (!FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
- throw new UnsupportedOperationException("Under development");
- }
- mRecoverableKeyStoreManager.startRemoteLockscreenValidation();
+ @NonNull
+ public StartLockscreenValidationRequest startRemoteLockscreenValidation() {
+ return mRecoverableKeyStoreManager.startRemoteLockscreenValidation(this);
}
/**
- * Verifies credentials guess from a remote device.
+ * Verifies encrypted credentials guess from a remote device.
*/
- public void validateRemoteLockscreen(@NonNull byte[] encryptedCredential) {
- if (!FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
- throw new UnsupportedOperationException("Under development");
- }
- mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential);
+ @NonNull
+ public RemoteLockscreenValidationResult
+ validateRemoteLockScreen2(@NonNull byte[] encryptedCredential) {
+ return mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential, this);
}
// Reading these settings needs the contacts permission
@@ -2602,43 +2618,112 @@
}
}
- private void onSyntheticPasswordKnown(@UserIdInt int userId, SyntheticPassword sp) {
+ private void onSyntheticPasswordCreated(@UserIdInt int userId, SyntheticPassword sp) {
+ onSyntheticPasswordKnown(userId, sp, true);
+ }
+
+ private void onSyntheticPasswordUnlocked(@UserIdInt int userId, SyntheticPassword sp) {
+ onSyntheticPasswordKnown(userId, sp, false);
+ }
+
+ private void onSyntheticPasswordKnown(
+ @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
if (mInjector.isGsiRunning()) {
Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
return;
}
- mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, sp.getVersion(),
- sp.getSyntheticPassword());
-
- callToAuthSecretIfNeeded(userId, sp);
+ mRebootEscrowManager.callToRebootEscrowIfNeeded(
+ userId, sp.getVersion(), sp.getSyntheticPassword());
+ callToAuthSecretIfNeeded(userId, sp, justCreated);
}
- private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
- // If the given user is the primary user, pass the auth secret to the HAL. Only the system
- // user can be primary. Check for the system user ID before calling getUserInfo(), as other
- // users may still be under construction.
+ /**
+ * Handles generation, storage, and sending of the vendor auth secret. Here we try to retrieve
+ * the auth secret to send it to the auth secret HAL, generate a fresh secret if need be, store
+ * it encrypted on disk so that the given user can unlock it in future, and stash it in memory
+ * so that when future users are created they can also unlock it.
+ *
+ * <p>Called whenever the SP of a user is available, except in GSI.
+ */
+ private void callToAuthSecretIfNeeded(
+ @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
if (mAuthSecretService == null) {
+ // If there's no IAuthSecret service, we don't need to maintain a auth secret
return;
}
- if (userId == UserHandle.USER_SYSTEM &&
- mUserManager.getUserInfo(userId).isPrimary()) {
- final byte[] secret = sp.deriveVendorAuthSecret();
- try {
- mAuthSecretService.setPrimaryUserCredential(secret);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
+ // User may be partially created, so use the internal user manager interface
+ final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
+ final UserInfo userInfo = userManagerInternal.getUserInfo(userId);
+ if (userInfo == null) {
+ // User may be partially deleted, skip this.
+ return;
+ }
+ final byte[] authSecret;
+ if (!mInjector.isHeadlessSystemUserMode()) {
+ // On non-headless systems, the auth secret is derived from user 0's
+ // SP, and only user 0 passes it to the HAL.
+ if (userId != USER_SYSTEM) {
+ return;
}
+ authSecret = sp.deriveVendorAuthSecret();
+ } else if (!mInjector.isMainUserPermanentAdmin() || !userInfo.isFull()) {
+ // Only full users can receive or pass on the auth secret.
+ // If there is no main permanent admin user, we don't try to create or send
+ // an auth secret, since there may sometimes be no full users.
+ return;
+ } else if (justCreated) {
+ if (userInfo.isMain()) {
+ // The first user is just being created, so we create a new auth secret
+ // at the same time.
+ Slog.i(TAG, "Generating new vendor auth secret and storing for user: " + userId);
+ authSecret = SecureRandomUtils.randomBytes(HEADLESS_VENDOR_AUTH_SECRET_LENGTH);
+ // Store it in memory, for when new users are created.
+ synchronized (mHeadlessAuthSecretLock) {
+ mAuthSecret = authSecret;
+ }
+ } else {
+ // A new user is being created. Another user should already have logged in at
+ // this point, and therefore the auth secret should be stored in memory.
+ synchronized (mHeadlessAuthSecretLock) {
+ authSecret = mAuthSecret;
+ }
+ if (authSecret == null) {
+ Slog.e(TAG, "Creating non-main user " + userId
+ + " but vendor auth secret is not in memory");
+ return;
+ }
+ }
+ // Store the auth secret encrypted using the user's SP (which was just created).
+ mSpManager.writeVendorAuthSecret(authSecret, sp, userId);
+ } else {
+ // The user already exists, so the auth secret should be stored encrypted
+ // with that user's SP.
+ authSecret = mSpManager.readVendorAuthSecret(sp, userId);
+ if (authSecret == null) {
+ Slog.e(TAG, "Unable to read vendor auth secret for user: " + userId);
+ return;
+ }
+ // Store it in memory, for when new users are created.
+ synchronized (mHeadlessAuthSecretLock) {
+ mAuthSecret = authSecret;
+ }
+ }
+ Slog.i(TAG, "Sending vendor auth secret to IAuthSecret HAL as user: " + userId);
+ try {
+ mAuthSecretService.setPrimaryUserCredential(authSecret);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to send vendor auth secret to IAuthSecret HAL", e);
}
}
/**
* Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and
* protects the user's CE key with a key derived from the SP.
- * <p>
- * This is called just once in the lifetime of the user: at user creation time (possibly delayed
- * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
- * or earlier where users with no LSKF didn't necessarily have an SP.
+ *
+ * <p>This is called just once in the lifetime of the user: at user creation time (possibly
+ * delayed until the time when Weaver is guaranteed to be available), or when upgrading from
+ * Android 13 or earlier where users with no LSKF didn't necessarily have an SP.
*/
@VisibleForTesting
SyntheticPassword initializeSyntheticPassword(int userId) {
@@ -2653,7 +2738,7 @@
LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
- onSyntheticPasswordKnown(userId, sp);
+ onSyntheticPasswordCreated(userId, sp);
return sp;
}
}
@@ -2720,7 +2805,7 @@
}
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
- onSyntheticPasswordKnown(userId, sp);
+ onSyntheticPasswordUnlocked(userId, sp);
}
private void setDeviceUnlockedForUser(int userId) {
@@ -3008,7 +3093,7 @@
+ "verification.");
return false;
}
- onSyntheticPasswordKnown(userId, result.syntheticPassword);
+ onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
return true;
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index ea000a0..c21c945 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -93,6 +94,9 @@
* while the LSKF is nonempty.
* SP_E0_NAME, SP_P1_NAME: Information needed to create and use escrow token-based protectors.
* Deleted when escrow token support is disabled for the user.
+ * VENDOR_AUTH_SECRET_NAME: A copy of the secret passed using the IAuthSecret interface,
+ * encrypted using a secret derived from the SP using
+ * PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY.
*
* For each protector, stored under the corresponding protector ID:
* SP_BLOB_NAME: The encrypted SP secret (the SP itself or the P0 value). Always exists.
@@ -120,6 +124,7 @@
private static final String PASSWORD_DATA_NAME = "pwd";
private static final String WEAVER_SLOT_NAME = "weaver";
private static final String PASSWORD_METRICS_NAME = "metrics";
+ private static final String VENDOR_AUTH_SECRET_NAME = "vendor_auth_secret";
// used for files associated with the SP itself, not with a particular protector
public static final long NULL_PROTECTOR_ID = 0L;
@@ -158,6 +163,8 @@
private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes();
+ private static final byte[] PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY =
+ "vendor-authsecret-encryption-key".getBytes();
private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
@@ -249,6 +256,10 @@
return deriveSubkey(PERSONALIZATION_PASSWORD_METRICS);
}
+ public byte[] deriveVendorAuthSecretEncryptionKey() {
+ return deriveSubkey(PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY);
+ }
+
/**
* Assigns escrow data to this synthetic password. This is a prerequisite to call
* {@link SyntheticPassword#recreateFromEscrow}.
@@ -1737,4 +1748,25 @@
mListeners.finishBroadcast();
}
}
+
+ public void writeVendorAuthSecret(
+ @NonNull final byte[] vendorAuthSecret,
+ @NonNull final SyntheticPassword sp,
+ @UserIdInt final int userId) {
+ final byte[] encrypted =
+ SyntheticPasswordCrypto.encrypt(
+ sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], vendorAuthSecret);
+ saveState(VENDOR_AUTH_SECRET_NAME, encrypted, NULL_PROTECTOR_ID, userId);
+ syncState(userId);
+ }
+
+ public @Nullable byte[] readVendorAuthSecret(
+ @NonNull final SyntheticPassword sp, @UserIdInt final int userId) {
+ final byte[] encrypted = loadState(VENDOR_AUTH_SECRET_NAME, NULL_PROTECTOR_ID, userId);
+ if (encrypted == null) {
+ return null;
+ }
+ return SyntheticPasswordCrypto.decrypt(
+ sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], encrypted);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index b437421..d981569 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -28,7 +28,10 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.KeyguardManager;
import android.app.PendingIntent;
+import android.app.RemoteLockscreenValidationResult;
+import android.app.StartLockscreenValidationRequest;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
@@ -40,11 +43,14 @@
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.ArrayMap;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
+import com.android.internal.widget.LockPatternUtils;
import com.android.security.SecureBox;
+import com.android.server.locksettings.LockSettingsService;
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -55,6 +61,8 @@
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage.LockscreenVerificationSession;
import java.io.IOException;
import java.security.InvalidKeyException;
@@ -103,6 +111,9 @@
private final ApplicationKeyStorage mApplicationKeyStorage;
private final TestOnlyInsecureCertificateHelper mTestCertHelper;
private final CleanupManager mCleanupManager;
+ // only set if SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API is enabled.
+ @Nullable private final RemoteLockscreenValidationSessionStorage
+ mRemoteLockscreenValidationSessionStorage;
/**
* Returns a new or existing instance.
@@ -112,7 +123,18 @@
public static synchronized RecoverableKeyStoreManager
getInstance(Context context) {
if (mInstance == null) {
- RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
+ RecoverableKeyStoreDb db;
+ RemoteLockscreenValidationSessionStorage lockscreenCheckSessions;
+ if (FeatureFlagUtils.isEnabled(context,
+ FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
+ // TODO(b/254335492): Remove flag check when feature is launched.
+ db = RecoverableKeyStoreDb.newInstance(context, 7);
+ lockscreenCheckSessions = new RemoteLockscreenValidationSessionStorage();
+ } else {
+ db = RecoverableKeyStoreDb.newInstance(context);
+ lockscreenCheckSessions = null;
+ }
+
PlatformKeyManager platformKeyManager;
ApplicationKeyStorage applicationKeyStorage;
try {
@@ -142,7 +164,8 @@
platformKeyManager,
applicationKeyStorage,
new TestOnlyInsecureCertificateHelper(),
- cleanupManager);
+ cleanupManager,
+ lockscreenCheckSessions);
}
return mInstance;
}
@@ -158,7 +181,8 @@
PlatformKeyManager platformKeyManager,
ApplicationKeyStorage applicationKeyStorage,
TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
- CleanupManager cleanupManager) {
+ CleanupManager cleanupManager,
+ RemoteLockscreenValidationSessionStorage remoteLockscreenValidationSessionStorage) {
mContext = context;
mDatabase = recoverableKeyStoreDb;
mRecoverySessionStorage = recoverySessionStorage;
@@ -177,6 +201,7 @@
Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
}
+ mRemoteLockscreenValidationSessionStorage = remoteLockscreenValidationSessionStorage;
}
/**
@@ -969,19 +994,50 @@
/**
* Starts a session to verify lock screen credentials provided by a remote device.
*/
- public void startRemoteLockscreenValidation() {
+ public StartLockscreenValidationRequest startRemoteLockscreenValidation(
+ LockSettingsService lockSettingService) {
+ if (mRemoteLockscreenValidationSessionStorage == null) {
+ throw new UnsupportedOperationException("Under development");
+ }
checkVerifyRemoteLockscreenPermission();
- // TODO(b/254335492): Create session in memory
- return;
+ int userId = UserHandle.getCallingUserId();
+ int savedCredentialType;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ savedCredentialType = lockSettingService.getCredentialType(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ int keyguardCredentailsType = lockPatternUtilsToKeyguardType(savedCredentialType);
+ LockscreenVerificationSession session =
+ mRemoteLockscreenValidationSessionStorage.startSession(userId);
+ PublicKey publicKey = session.getKeyPair().getPublic();
+ byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
+ int badGuesses = mDatabase.getBadRemoteGuessCounter(userId);
+ return new StartLockscreenValidationRequest.Builder()
+ .setLockscreenUiType(keyguardCredentailsType)
+ .setSourcePublicKey(new byte[]{})
+ .build();
}
/**
* Verifies encrypted credentials guess from a remote device.
*/
- public void validateRemoteLockscreen(@NonNull byte[] encryptedCredential) {
+ public RemoteLockscreenValidationResult validateRemoteLockscreen(
+ @NonNull byte[] encryptedCredential,
+ LockSettingsService lockSettingService) {
+ if (mRemoteLockscreenValidationSessionStorage == null) {
+ throw new UnsupportedOperationException("Under development");
+ }
checkVerifyRemoteLockscreenPermission();
- // TODO(b/254335492): Decrypt and verify credentials
- return;
+ int userId = UserHandle.getCallingUserId();
+ LockscreenVerificationSession session =
+ mRemoteLockscreenValidationSessionStorage.get(userId);
+ if (session == null) {
+ throw new IllegalStateException("There is no active lock screen check session");
+ }
+ // TODO(b/254335492): Call lockSettingService.verifyCredential
+ return new RemoteLockscreenValidationResult.Builder().build();
}
private void checkVerifyRemoteLockscreenPermission() {
@@ -995,6 +1051,21 @@
mCleanupManager.registerRecoveryAgent(userId, uid);
}
+ private int lockPatternUtilsToKeyguardType(int credentialsType) {
+ switch(credentialsType) {
+ case LockPatternUtils.CREDENTIAL_TYPE_NONE:
+ throw new IllegalStateException("Screen lock is not set");
+ case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
+ return KeyguardManager.PATTERN;
+ case LockPatternUtils.CREDENTIAL_TYPE_PIN:
+ return KeyguardManager.PIN;
+ case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
+ return KeyguardManager.PASSWORD;
+ default:
+ throw new IllegalStateException("Screen lock is not set");
+ }
+ }
+
private void checkRecoverKeyStorePermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.RECOVER_KEYSTORE,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 225d2a4..3a21c34 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -262,7 +262,7 @@
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
info.sendSystemPackageUpdatedBroadcasts();
- PackageMetrics.onUninstallSucceeded(info, deleteFlags, userId);
+ PackageMetrics.onUninstallSucceeded(info, deleteFlags, removeUser);
}
// Force a gc to clear up things.
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 1ed5999..9384300 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2570,15 +2570,19 @@
if (disabledPs != null) {
dataOwnerPkg = disabledPs.getPkg();
}
- try {
- PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
- } catch (PackageManagerException e) {
- String errorMsg = "System app: " + packageName + " cannot be downgraded to"
- + " older than its preloaded version on the system image. "
- + e.getMessage();
- Slog.w(TAG, errorMsg);
- return Pair.create(
- PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+ if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) {
+ // Only restrict non-debuggable builds and non-debuggable version of the app
+ try {
+ PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
+ } catch (PackageManagerException e) {
+ String errorMsg =
+ "System app: " + packageName + " cannot be downgraded to"
+ + " older than its preloaded version on the system"
+ + " image. " + e.getMessage();
+ Slog.w(TAG, errorMsg);
+ return Pair.create(
+ PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f77d38f..55dcaf6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3996,7 +3996,14 @@
return;
}
}
- r.run();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // This will call into StagingManager which might trigger external callbacks
+ r.run();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9c91879..7e7205d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -120,7 +120,7 @@
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
-import com.android.server.security.FileIntegrityLocal;
+import com.android.server.security.FileIntegrity;
import com.android.server.utils.Slogf;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
@@ -2714,8 +2714,8 @@
}
try {
- FileIntegrityLocal.setUpFsVerity(mSettingsFilename.getAbsolutePath());
- FileIntegrityLocal.setUpFsVerity(mSettingsReserveCopyFilename.getAbsolutePath());
+ FileIntegrity.setUpFsVerity(mSettingsFilename);
+ FileIntegrity.setUpFsVerity(mSettingsReserveCopyFilename);
} catch (IOException e) {
Slog.e(TAG, "Failed to verity-protect settings", e);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 26a990c..a9edce1 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -553,11 +553,12 @@
* switched to.
*
* <p>Otherwise, in {@link UserManager#isHeadlessSystemUserMode() headless system user mode},
- * this will be the user who was last in the foreground on this device. If there is no
- * switchable user on the device, a new user will be created and its id will be returned.
+ * this will be the user who was last in the foreground on this device.
*
- * <p>In non-headless system user mode, the return value will be {@link UserHandle#USER_SYSTEM}.
+ * <p>In non-headless system user mode, the return value will be
+ * {@link android.os.UserHandle#USER_SYSTEM}.
+
+ * @throws UserManager.CheckedUserOperationException if no switchable user can be found
*/
- public abstract @UserIdInt int getBootUser()
- throws UserManager.CheckedUserOperationException;
+ public abstract @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 762d1f6..ce7dc5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5049,6 +5049,8 @@
//...then external ones
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ // In HSUM, MainUser might be created before PHASE_ACTIVITY_MANAGER_READY has been sent.
+ addedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
// Also, add the UserHandle for mainline modules which can't use the @hide
// EXTRA_USER_HANDLE.
@@ -6758,18 +6760,6 @@
return mLocalService.isUserInitialized(userId);
}
- /**
- * Creates a new user, intended to be the initial user on a device in headless system user mode.
- */
- private UserInfo createInitialUserForHsum() throws UserManager.CheckedUserOperationException {
- final int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
-
- // Null name will be replaced with "Owner" on-demand to allow for localisation.
- return createUserInternalUnchecked(/* name= */ null, UserManager.USER_TYPE_FULL_SECONDARY,
- flags, UserHandle.USER_NULL, /* preCreate= */ false,
- /* disallowedPackages= */ null, /* token= */ null);
- }
-
private class LocalService extends UserManagerInternal {
@Override
public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
@@ -7249,15 +7239,9 @@
}
}
}
- // No switchable users. Create the initial user.
- final UserInfo newInitialUser = createInitialUserForHsum();
- if (newInitialUser == null) {
- throw new UserManager.CheckedUserOperationException(
- "Initial user creation failed", USER_OPERATION_ERROR_UNKNOWN);
- }
- Slogf.i(LOG_TAG,
- "No switchable users. Boot user is new user %d", newInitialUser.id);
- return newInitialUser.id;
+ // No switchable users found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
}
// Not HSUM, return system user.
return UserHandle.USER_SYSTEM;
@@ -7437,14 +7421,14 @@
/**
* Returns true, when user has {@link UserInfo#FLAG_MAIN} and system property
- * {@link com.android.internal.R.bool.isMainUserPermanentAdmin} is true.
+ * {@link com.android.internal.R.bool#config_isMainUserPermanentAdmin} is true.
*/
private boolean isNonRemovableMainUser(UserInfo userInfo) {
return userInfo.isMain() && isMainUserPermanentAdmin();
}
/**
- * Returns true, when {@link com.android.internal.R.bool.isMainUserPermanentAdmin} is true.
+ * Returns true if {@link com.android.internal.R.bool#config_isMainUserPermanentAdmin} is true.
* If the main user is a permanent admin user it can't be deleted
* or downgraded to non-admin status.
*/
diff --git a/services/core/java/com/android/server/security/FileIntegrityLocal.java b/services/core/java/com/android/server/security/FileIntegrity.java
similarity index 63%
rename from services/core/java/com/android/server/security/FileIntegrityLocal.java
rename to services/core/java/com/android/server/security/FileIntegrity.java
index 8c7219b..7b87d99 100644
--- a/services/core/java/com/android/server/security/FileIntegrityLocal.java
+++ b/services/core/java/com/android/server/security/FileIntegrity.java
@@ -18,19 +18,22 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.ParcelFileDescriptor;
import com.android.internal.security.VerityUtils;
+import java.io.File;
import java.io.IOException;
+
/**
* In-process API for server side FileIntegrity related infrastructure.
*
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public final class FileIntegrityLocal {
- private FileIntegrityLocal() {}
+public final class FileIntegrity {
+ private FileIntegrity() {}
/**
* Enables fs-verity, if supported by the filesystem.
@@ -38,7 +41,18 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- public static void setUpFsVerity(@NonNull String filePath) throws IOException {
- VerityUtils.setUpFsverity(filePath);
+ public static void setUpFsVerity(@NonNull File file) throws IOException {
+ VerityUtils.setUpFsverity(file.getAbsolutePath());
+ }
+
+ /**
+ * Enables fs-verity, if supported by the filesystem.
+ * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public static void setUpFsVerity(@NonNull ParcelFileDescriptor parcelFileDescriptor)
+ throws IOException {
+ VerityUtils.setUpFsverity(parcelFileDescriptor.getFd());
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 625e7d9..6763514 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -30,7 +30,7 @@
import android.content.ComponentName;
import android.graphics.Rect;
import android.os.RemoteCallbackList;
-import android.util.ArrayMap;
+import android.util.SparseArray;
import java.io.File;
@@ -115,7 +115,7 @@
* A map to keep track of the dimming set by different applications. The key is the calling
* UID and the value is the dim amount.
*/
- ArrayMap<Integer, Float> mUidToDimAmount = new ArrayMap<>();
+ SparseArray<Float> mUidToDimAmount = new SparseArray<>();
/**
* Whether we need to extract the wallpaper colors again to calculate the dark hints
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
new file mode 100644
index 0000000..07a7837
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
+import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+
+import android.annotation.Nullable;
+import android.app.WallpaperColors;
+import android.app.backup.WallpaperBackupHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.FileUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.JournaledFile;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper for the wallpaper loading / saving / xml parsing
+ * Only meant to be used lock held by WallpaperManagerService
+ * Only meant to be instantiated once by WallpaperManagerService
+ */
+class WallpaperDataParser {
+
+ private static final String TAG = WallpaperDataParser.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private final ComponentName mImageWallpaper;
+ private final WallpaperDisplayHelper mWallpaperDisplayHelper;
+ private final WallpaperCropper mWallpaperCropper;
+ private final Context mContext;
+
+ WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper,
+ WallpaperCropper wallpaperCropper) {
+ mContext = context;
+ mWallpaperDisplayHelper = wallpaperDisplayHelper;
+ mWallpaperCropper = wallpaperCropper;
+ mImageWallpaper = ComponentName.unflattenFromString(
+ context.getResources().getString(R.string.image_wallpaper_component));
+ }
+
+ private JournaledFile makeJournaledFile(int userId) {
+ final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
+ return new JournaledFile(new File(base), new File(base + ".tmp"));
+ }
+
+ static class WallpaperLoadingResult {
+
+ private final WallpaperData mSystemWallpaperData;
+
+ @Nullable
+ private final WallpaperData mLockWallpaperData;
+
+ private final boolean mSuccess;
+
+ private WallpaperLoadingResult(
+ WallpaperData systemWallpaperData,
+ WallpaperData lockWallpaperData,
+ boolean success) {
+ mSystemWallpaperData = systemWallpaperData;
+ mLockWallpaperData = lockWallpaperData;
+ mSuccess = success;
+ }
+
+ public WallpaperData getSystemWallpaperData() {
+ return mSystemWallpaperData;
+ }
+
+ public WallpaperData getLockWallpaperData() {
+ return mLockWallpaperData;
+ }
+
+ public boolean success() {
+ return mSuccess;
+ }
+ }
+
+ /**
+ * Load the system wallpaper (and the lock wallpaper, if it exists) from disk
+ * @param userId the id of the user for which the wallpaper should be loaded
+ * @param keepDimensionHints if false, parse and set the
+ * {@link DisplayData} width and height for the specified userId
+ * @param wallpaper the wallpaper object to reuse to do the modifications.
+ * If null, a new object will be created.
+ * @param lockWallpaper the lock wallpaper object to reuse to do the modifications.
+ * If null, a new object will be created.
+ * @return a {@link WallpaperLoadingResult} object containing the wallpaper data.
+ * This object will contain the {@code wallpaper} and
+ * {@code lockWallpaper} provided as parameters, if they are not null.
+ */
+ public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
+ WallpaperData wallpaper, WallpaperData lockWallpaper) {
+ JournaledFile journal = makeJournaledFile(userId);
+ FileInputStream stream = null;
+ File file = journal.chooseForRead();
+
+ if (wallpaper == null) {
+ // Do this once per boot
+ migrateFromOld();
+ wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
+ wallpaper.allowBackup = true;
+ if (!wallpaper.cropExists()) {
+ if (wallpaper.sourceExists()) {
+ mWallpaperCropper.generateCrop(wallpaper);
+ } else {
+ Slog.i(TAG, "No static wallpaper imagery; defaults will be shown");
+ }
+ }
+ }
+
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ boolean success = false;
+
+ try {
+ stream = new FileInputStream(file);
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+
+ int type;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG) {
+ String tag = parser.getName();
+ if ("wp".equals(tag)) {
+ // Common to system + lock wallpapers
+ parseWallpaperAttributes(parser, wallpaper, keepDimensionHints);
+
+ // A system wallpaper might also be a live wallpaper
+ String comp = parser.getAttributeValue(null, "component");
+ wallpaper.nextWallpaperComponent = comp != null
+ ? ComponentName.unflattenFromString(comp)
+ : null;
+ if (wallpaper.nextWallpaperComponent == null
+ || "android".equals(wallpaper.nextWallpaperComponent
+ .getPackageName())) {
+ wallpaper.nextWallpaperComponent = mImageWallpaper;
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "mWidth:" + wpdData.mWidth);
+ Slog.v(TAG, "mHeight:" + wpdData.mHeight);
+ Slog.v(TAG, "cropRect:" + wallpaper.cropHint);
+ Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors);
+ Slog.v(TAG, "mName:" + wallpaper.name);
+ Slog.v(TAG, "mNextWallpaperComponent:"
+ + wallpaper.nextWallpaperComponent);
+ }
+ } else if ("kwp".equals(tag)) {
+ // keyguard-specific wallpaper for this user
+
+ if (lockWallpaper == null) {
+ lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
+ }
+ parseWallpaperAttributes(parser, lockWallpaper, false);
+ }
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+ success = true;
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "no current wallpaper -- first boot?");
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ }
+ IoUtils.closeQuietly(stream);
+
+ if (!success) {
+ wallpaper.cropHint.set(0, 0, 0, 0);
+ wpdData.mPadding.set(0, 0, 0, 0);
+ wallpaper.name = "";
+ lockWallpaper = null;
+ } else {
+ if (wallpaper.wallpaperId <= 0) {
+ wallpaper.wallpaperId = makeWallpaperIdLocked();
+ if (DEBUG) {
+ Slog.w(TAG, "Didn't set wallpaper id in loadSettingsLocked(" + userId
+ + "); now " + wallpaper.wallpaperId);
+ }
+ }
+ }
+
+ mWallpaperDisplayHelper.ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
+ ensureSaneWallpaperData(wallpaper);
+ if (lockWallpaper != null) {
+ ensureSaneWallpaperData(lockWallpaper);
+ lockWallpaper.mWhich = FLAG_LOCK;
+ wallpaper.mWhich = FLAG_SYSTEM;
+ } else {
+ wallpaper.mWhich = FLAG_SYSTEM | FLAG_LOCK;
+ }
+
+ return new WallpaperLoadingResult(wallpaper, lockWallpaper, success);
+ }
+
+ private void ensureSaneWallpaperData(WallpaperData wallpaper) {
+ // Only overwrite cropHint if the rectangle is invalid.
+ if (wallpaper.cropHint.width() < 0
+ || wallpaper.cropHint.height() < 0) {
+ wallpaper.cropHint.set(0, 0, 0, 0);
+ }
+ }
+
+
+ private void migrateFromOld() {
+ // Pre-N, what existed is the one we're now using as the display crop
+ File preNWallpaper = new File(getWallpaperDir(0), WALLPAPER_CROP);
+ // In the very-long-ago, imagery lived with the settings app
+ File originalWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
+ File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
+
+ // Migrations from earlier wallpaper image storage schemas
+ if (preNWallpaper.exists()) {
+ if (!newWallpaper.exists()) {
+ // we've got the 'wallpaper' crop file but not the nominal source image,
+ // so do the simple "just take everything" straight copy of legacy data
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating wallpaper schema");
+ }
+ FileUtils.copyFile(preNWallpaper, newWallpaper);
+ } // else we're in the usual modern case: both source & crop exist
+ } else if (originalWallpaper.exists()) {
+ // VERY old schema; make sure things exist and are in the right place
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating antique wallpaper schema");
+ }
+ File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
+ if (oldInfo.exists()) {
+ File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
+ oldInfo.renameTo(newInfo);
+ }
+
+ FileUtils.copyFile(originalWallpaper, preNWallpaper);
+ originalWallpaper.renameTo(newWallpaper);
+ }
+ }
+
+ @VisibleForTesting
+ void parseWallpaperAttributes(TypedXmlPullParser parser, WallpaperData wallpaper,
+ boolean keepDimensionHints) throws XmlPullParserException {
+ final int id = parser.getAttributeInt(null, "id", -1);
+ if (id != -1) {
+ wallpaper.wallpaperId = id;
+ if (id > WallpaperUtils.getCurrentWallpaperId()) {
+ WallpaperUtils.setCurrentWallpaperId(id);
+ }
+ } else {
+ wallpaper.wallpaperId = makeWallpaperIdLocked();
+ }
+
+ final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+
+ if (!keepDimensionHints) {
+ wpData.mWidth = parser.getAttributeInt(null, "width");
+ wpData.mHeight = parser.getAttributeInt(null, "height");
+ }
+ wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
+ wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
+ wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
+ wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
+ wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
+ wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
+ wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
+ wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+ wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
+ int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0);
+ if (dimAmountsCount > 0) {
+ SparseArray<Float> allDimAmounts = new SparseArray<>(dimAmountsCount);
+ for (int i = 0; i < dimAmountsCount; i++) {
+ int uid = getAttributeInt(parser, "dimUID" + i, 0);
+ float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f);
+ allDimAmounts.put(uid, dimValue);
+ }
+ wallpaper.mUidToDimAmount = allDimAmounts;
+ }
+ int colorsCount = getAttributeInt(parser, "colorsCount", 0);
+ int allColorsCount = getAttributeInt(parser, "allColorsCount", 0);
+ if (allColorsCount > 0) {
+ Map<Integer, Integer> allColors = new HashMap<>(allColorsCount);
+ for (int i = 0; i < allColorsCount; i++) {
+ int colorInt = getAttributeInt(parser, "allColorsValue" + i, 0);
+ int population = getAttributeInt(parser, "allColorsPopulation" + i, 0);
+ allColors.put(colorInt, population);
+ }
+ int colorHints = getAttributeInt(parser, "colorHints", 0);
+ wallpaper.primaryColors = new WallpaperColors(allColors, colorHints);
+ } else if (colorsCount > 0) {
+ Color primary = null, secondary = null, tertiary = null;
+ for (int i = 0; i < colorsCount; i++) {
+ Color color = Color.valueOf(getAttributeInt(parser, "colorValue" + i, 0));
+ if (i == 0) {
+ primary = color;
+ } else if (i == 1) {
+ secondary = color;
+ } else if (i == 2) {
+ tertiary = color;
+ } else {
+ break;
+ }
+ }
+ int colorHints = getAttributeInt(parser, "colorHints", 0);
+ wallpaper.primaryColors = new WallpaperColors(primary, secondary, tertiary, colorHints);
+ }
+ wallpaper.name = parser.getAttributeValue(null, "name");
+ wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
+ }
+
+ private int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
+ return parser.getAttributeInt(null, name, defValue);
+ }
+
+ private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
+ return parser.getAttributeFloat(null, name, defValue);
+ }
+
+ void saveSettingsLocked(int userId, WallpaperData wallpaper, WallpaperData lockWallpaper) {
+ JournaledFile journal = makeJournaledFile(userId);
+ FileOutputStream fstream = null;
+ try {
+ fstream = new FileOutputStream(journal.chooseForWrite(), false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+
+ if (wallpaper != null) {
+ writeWallpaperAttributes(out, "wp", wallpaper);
+ }
+
+ if (lockWallpaper != null) {
+ writeWallpaperAttributes(out, "kwp", lockWallpaper);
+ }
+
+ out.endDocument();
+
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+ journal.commit();
+ } catch (IOException e) {
+ IoUtils.closeQuietly(fstream);
+ journal.rollback();
+ }
+ }
+
+ @VisibleForTesting
+ void writeWallpaperAttributes(TypedXmlSerializer out, String tag, WallpaperData wallpaper)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ if (DEBUG) {
+ Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
+ }
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ out.startTag(null, tag);
+ out.attributeInt(null, "id", wallpaper.wallpaperId);
+ out.attributeInt(null, "width", wpdData.mWidth);
+ out.attributeInt(null, "height", wpdData.mHeight);
+
+ out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
+ out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
+ out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
+ out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+
+ if (wpdData.mPadding.left != 0) {
+ out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
+ }
+ if (wpdData.mPadding.top != 0) {
+ out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
+ }
+ if (wpdData.mPadding.right != 0) {
+ out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
+ }
+ if (wpdData.mPadding.bottom != 0) {
+ out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+ }
+
+ out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
+ int dimAmountsCount = wallpaper.mUidToDimAmount.size();
+ out.attributeInt(null, "dimAmountsCount", dimAmountsCount);
+ if (dimAmountsCount > 0) {
+ int index = 0;
+ for (int i = 0; i < wallpaper.mUidToDimAmount.size(); i++) {
+ out.attributeInt(null, "dimUID" + index, wallpaper.mUidToDimAmount.keyAt(i));
+ out.attributeFloat(null, "dimValue" + index, wallpaper.mUidToDimAmount.valueAt(i));
+ index++;
+ }
+ }
+
+ if (wallpaper.primaryColors != null) {
+ int colorsCount = wallpaper.primaryColors.getMainColors().size();
+ out.attributeInt(null, "colorsCount", colorsCount);
+ if (colorsCount > 0) {
+ for (int i = 0; i < colorsCount; i++) {
+ final Color wc = wallpaper.primaryColors.getMainColors().get(i);
+ out.attributeInt(null, "colorValue" + i, wc.toArgb());
+ }
+ }
+
+ int allColorsCount = wallpaper.primaryColors.getAllColors().size();
+ out.attributeInt(null, "allColorsCount", allColorsCount);
+ if (allColorsCount > 0) {
+ int index = 0;
+ for (Map.Entry<Integer, Integer> entry : wallpaper.primaryColors.getAllColors()
+ .entrySet()) {
+ out.attributeInt(null, "allColorsValue" + index, entry.getKey());
+ out.attributeInt(null, "allColorsPopulation" + index, entry.getValue());
+ index++;
+ }
+ }
+
+ out.attributeInt(null, "colorHints", wallpaper.primaryColors.getColorHints());
+ }
+
+ out.attribute(null, "name", wallpaper.name);
+ if (wallpaper.wallpaperComponent != null
+ && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
+ out.attribute(null, "component",
+ wallpaper.wallpaperComponent.flattenToShortString());
+ }
+
+ if (wallpaper.allowBackup) {
+ out.attributeBoolean(null, "backup", true);
+ }
+
+ out.endTag(null, tag);
+ }
+
+ // Restore the named resource bitmap to both source + crop files
+ boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
+ if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
+ String resName = wallpaper.name.substring(4);
+
+ String pkg = null;
+ int colon = resName.indexOf(':');
+ if (colon > 0) {
+ pkg = resName.substring(0, colon);
+ }
+
+ String ident = null;
+ int slash = resName.lastIndexOf('/');
+ if (slash > 0) {
+ ident = resName.substring(slash + 1);
+ }
+
+ String type = null;
+ if (colon > 0 && slash > 0 && (slash - colon) > 1) {
+ type = resName.substring(colon + 1, slash);
+ }
+
+ if (pkg != null && ident != null && type != null) {
+ int resId = -1;
+ InputStream res = null;
+ FileOutputStream fos = null;
+ FileOutputStream cos = null;
+ try {
+ Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
+ Resources r = c.getResources();
+ resId = r.getIdentifier(resName, null, null);
+ if (resId == 0) {
+ Slog.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
+ + " ident=" + ident);
+ return false;
+ }
+
+ res = r.openRawResource(resId);
+ if (wallpaper.wallpaperFile.exists()) {
+ wallpaper.wallpaperFile.delete();
+ wallpaper.cropFile.delete();
+ }
+ fos = new FileOutputStream(wallpaper.wallpaperFile);
+ cos = new FileOutputStream(wallpaper.cropFile);
+
+ byte[] buffer = new byte[32768];
+ int amt;
+ while ((amt = res.read(buffer)) > 0) {
+ fos.write(buffer, 0, amt);
+ cos.write(buffer, 0, amt);
+ }
+ // mWallpaperObserver will notice the close and send the change broadcast
+
+ Slog.v(TAG, "Restored wallpaper: " + resName);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Package name " + pkg + " not found");
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Resource not found: " + resId);
+ } catch (IOException e) {
+ Slog.e(TAG, "IOException while restoring wallpaper ", e);
+ } finally {
+ IoUtils.closeQuietly(res);
+ if (fos != null) {
+ FileUtils.sync(fos);
+ }
+ if (cos != null) {
+ FileUtils.sync(cos);
+ }
+ IoUtils.closeQuietly(fos);
+ IoUtils.closeQuietly(cos);
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index f02ee66..3f02266 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -30,6 +30,7 @@
import com.android.server.wm.WindowManagerInternal;
import java.util.function.Consumer;
+
/**
* Internal class used to store all the display data relevant to the wallpapers
*/
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5fd57e3..198c339 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -35,7 +35,6 @@
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
-import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
@@ -56,7 +55,6 @@
import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.admin.DevicePolicyManagerInternal;
-import android.app.backup.WallpaperBackupHelper;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -70,10 +68,8 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
@@ -101,12 +97,10 @@
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
-import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.Xml;
import android.view.Display;
import com.android.internal.R;
@@ -114,9 +108,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.JournaledFile;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -125,22 +116,16 @@
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.WindowManagerInternal;
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -152,7 +137,6 @@
private static final String TAG = "WallpaperManagerService";
private static final boolean DEBUG = false;
private static final boolean DEBUG_LIVE = true;
- private static final boolean DEBUG_CROP = true;
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
@@ -849,6 +833,9 @@
private LocalColorRepository mLocalColorRepo = new LocalColorRepository();
@VisibleForTesting
+ final WallpaperDataParser mWallpaperDataParser;
+
+ @VisibleForTesting
final WallpaperDisplayHelper mWallpaperDisplayHelper;
final WallpaperCropper mWallpaperCropper;
@@ -1612,6 +1599,8 @@
dm.registerDisplayListener(mDisplayListener, null /* handler */);
mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ mWallpaperDataParser = new WallpaperDataParser(
+ mContext, mWallpaperDisplayHelper, mWallpaperCropper);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mMonitor = new MyPackageMonitor();
mColorsChangedListeners = new SparseArray<>();
@@ -1899,7 +1888,6 @@
// bound into place
wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
final WallpaperData fallback = new WallpaperData(wallpaper.userId, FLAG_LOCK);
- ensureSaneWallpaperData(fallback);
bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
mWaitingForUnlock = true;
}
@@ -2630,12 +2618,10 @@
*
* @param uidToDimAmountMap Map of UIDs to dim amounts
*/
- private float getHighestDimAmountFromMap(ArrayMap<Integer, Float> uidToDimAmountMap) {
+ private float getHighestDimAmountFromMap(SparseArray<Float> uidToDimAmountMap) {
float maxDimAmount = 0.0f;
- for (Map.Entry<Integer, Float> entry : uidToDimAmountMap.entrySet()) {
- if (entry.getValue() > maxDimAmount) {
- maxDimAmount = entry.getValue();
- }
+ for (int i = 0; i < uidToDimAmountMap.size(); i++) {
+ maxDimAmount = Math.max(maxDimAmount, uidToDimAmountMap.valueAt(i));
}
return maxDimAmount;
}
@@ -3432,169 +3418,14 @@
}
}
- private JournaledFile makeJournaledFile(int userId) {
- final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
- return new JournaledFile(new File(base), new File(base + ".tmp"));
- }
-
void saveSettingsLocked(int userId) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("WPMS.saveSettingsLocked-" + userId);
- JournaledFile journal = makeJournaledFile(userId);
- FileOutputStream fstream = null;
- try {
- fstream = new FileOutputStream(journal.chooseForWrite(), false);
- TypedXmlSerializer out = Xml.resolveSerializer(fstream);
- out.startDocument(null, true);
-
- WallpaperData wallpaper;
-
- wallpaper = mWallpaperMap.get(userId);
- if (wallpaper != null) {
- writeWallpaperAttributes(out, "wp", wallpaper);
- }
- wallpaper = mLockWallpaperMap.get(userId);
- if (wallpaper != null) {
- writeWallpaperAttributes(out, "kwp", wallpaper);
- }
-
- out.endDocument();
-
- fstream.flush();
- FileUtils.sync(fstream);
- fstream.close();
- journal.commit();
- } catch (IOException e) {
- IoUtils.closeQuietly(fstream);
- journal.rollback();
- }
+ mWallpaperDataParser.saveSettingsLocked(
+ userId, mWallpaperMap.get(userId), mLockWallpaperMap.get(userId));
t.traceEnd();
}
-
- @VisibleForTesting
- void writeWallpaperAttributes(TypedXmlSerializer out, String tag,
- WallpaperData wallpaper)
- throws IllegalArgumentException, IllegalStateException, IOException {
- if (DEBUG) {
- Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
- }
- final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
- out.startTag(null, tag);
- out.attributeInt(null, "id", wallpaper.wallpaperId);
- out.attributeInt(null, "width", wpdData.mWidth);
- out.attributeInt(null, "height", wpdData.mHeight);
-
- out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
- out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
- out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
- out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
-
- if (wpdData.mPadding.left != 0) {
- out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
- }
- if (wpdData.mPadding.top != 0) {
- out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
- }
- if (wpdData.mPadding.right != 0) {
- out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
- }
- if (wpdData.mPadding.bottom != 0) {
- out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
- }
-
- out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
- int dimAmountsCount = wallpaper.mUidToDimAmount.size();
- out.attributeInt(null, "dimAmountsCount", dimAmountsCount);
- if (dimAmountsCount > 0) {
- int index = 0;
- for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) {
- out.attributeInt(null, "dimUID" + index, entry.getKey());
- out.attributeFloat(null, "dimValue" + index, entry.getValue());
- index++;
- }
- }
-
- if (wallpaper.primaryColors != null) {
- int colorsCount = wallpaper.primaryColors.getMainColors().size();
- out.attributeInt(null, "colorsCount", colorsCount);
- if (colorsCount > 0) {
- for (int i = 0; i < colorsCount; i++) {
- final Color wc = wallpaper.primaryColors.getMainColors().get(i);
- out.attributeInt(null, "colorValue" + i, wc.toArgb());
- }
- }
-
- int allColorsCount = wallpaper.primaryColors.getAllColors().size();
- out.attributeInt(null, "allColorsCount", allColorsCount);
- if (allColorsCount > 0) {
- int index = 0;
- for (Map.Entry<Integer, Integer> entry : wallpaper.primaryColors.getAllColors()
- .entrySet()) {
- out.attributeInt(null, "allColorsValue" + index, entry.getKey());
- out.attributeInt(null, "allColorsPopulation" + index, entry.getValue());
- index++;
- }
- }
-
- out.attributeInt(null, "colorHints", wallpaper.primaryColors.getColorHints());
- }
-
- out.attribute(null, "name", wallpaper.name);
- if (wallpaper.wallpaperComponent != null
- && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
- out.attribute(null, "component",
- wallpaper.wallpaperComponent.flattenToShortString());
- }
-
- if (wallpaper.allowBackup) {
- out.attributeBoolean(null, "backup", true);
- }
-
- out.endTag(null, tag);
- }
-
- private void migrateFromOld() {
- // Pre-N, what existed is the one we're now using as the display crop
- File preNWallpaper = new File(getWallpaperDir(0), WALLPAPER_CROP);
- // In the very-long-ago, imagery lived with the settings app
- File originalWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
- File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
-
- // Migrations from earlier wallpaper image storage schemas
- if (preNWallpaper.exists()) {
- if (!newWallpaper.exists()) {
- // we've got the 'wallpaper' crop file but not the nominal source image,
- // so do the simple "just take everything" straight copy of legacy data
- if (DEBUG) {
- Slog.i(TAG, "Migrating wallpaper schema");
- }
- FileUtils.copyFile(preNWallpaper, newWallpaper);
- } // else we're in the usual modern case: both source & crop exist
- } else if (originalWallpaper.exists()) {
- // VERY old schema; make sure things exist and are in the right place
- if (DEBUG) {
- Slog.i(TAG, "Migrating antique wallpaper schema");
- }
- File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
- if (oldInfo.exists()) {
- File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
- oldInfo.renameTo(newInfo);
- }
-
- FileUtils.copyFile(originalWallpaper, preNWallpaper);
- originalWallpaper.renameTo(newWallpaper);
- }
- }
-
- private int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
- return parser.getAttributeInt(null, name, defValue);
- }
-
- private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
- return parser.getAttributeFloat(null, name, defValue);
- }
-
/**
* Determines and returns the current wallpaper for the given user and destination, creating
* a valid entry if it does not already exist and adding it to the appropriate wallpaper map.
@@ -3629,14 +3460,12 @@
if (which == FLAG_LOCK) {
wallpaper = new WallpaperData(userId, FLAG_LOCK);
mLockWallpaperMap.put(userId, wallpaper);
- ensureSaneWallpaperData(wallpaper);
} else {
// rationality fallback: we're in bad shape, but establishing a known
// valid system+lock WallpaperData will keep us from dying.
Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!");
wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
mWallpaperMap.put(userId, wallpaper);
- ensureSaneWallpaperData(wallpaper);
}
}
}
@@ -3644,114 +3473,17 @@
}
private void loadSettingsLocked(int userId, boolean keepDimensionHints) {
- JournaledFile journal = makeJournaledFile(userId);
- FileInputStream stream = null;
- File file = journal.chooseForRead();
+ initializeFallbackWallpaper();
+ WallpaperData wallpaperData = mWallpaperMap.get(userId);
+ WallpaperData lockWallpaperData = mLockWallpaperMap.get(userId);
+ WallpaperDataParser.WallpaperLoadingResult result = mWallpaperDataParser.loadSettingsLocked(
+ userId, keepDimensionHints, wallpaperData, lockWallpaperData);
- WallpaperData wallpaper = mWallpaperMap.get(userId);
- if (wallpaper == null) {
- // Do this once per boot
- migrateFromOld();
-
- wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
- wallpaper.allowBackup = true;
- mWallpaperMap.put(userId, wallpaper);
- if (!wallpaper.cropExists()) {
- if (wallpaper.sourceExists()) {
- mWallpaperCropper.generateCrop(wallpaper);
- } else {
- Slog.i(TAG, "No static wallpaper imagery; defaults will be shown");
- }
- }
- initializeFallbackWallpaper();
- }
- boolean success = false;
- final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
- try {
- stream = new FileInputStream(file);
- TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-
- int type;
- do {
- type = parser.next();
- if (type == XmlPullParser.START_TAG) {
- String tag = parser.getName();
- if ("wp".equals(tag)) {
- // Common to system + lock wallpapers
- parseWallpaperAttributes(parser, wallpaper, keepDimensionHints);
-
- // A system wallpaper might also be a live wallpaper
- String comp = parser.getAttributeValue(null, "component");
- wallpaper.nextWallpaperComponent = comp != null
- ? ComponentName.unflattenFromString(comp)
- : null;
- if (wallpaper.nextWallpaperComponent == null
- || "android".equals(wallpaper.nextWallpaperComponent
- .getPackageName())) {
- wallpaper.nextWallpaperComponent = mImageWallpaper;
- }
-
- if (DEBUG) {
- Slog.v(TAG, "mWidth:" + wpdData.mWidth);
- Slog.v(TAG, "mHeight:" + wpdData.mHeight);
- Slog.v(TAG, "cropRect:" + wallpaper.cropHint);
- Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors);
- Slog.v(TAG, "mName:" + wallpaper.name);
- Slog.v(TAG, "mNextWallpaperComponent:"
- + wallpaper.nextWallpaperComponent);
- }
- } else if ("kwp".equals(tag)) {
- // keyguard-specific wallpaper for this user
- WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
- if (lockWallpaper == null) {
- lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
- mLockWallpaperMap.put(userId, lockWallpaper);
- }
- parseWallpaperAttributes(parser, lockWallpaper, false);
- }
- }
- } while (type != XmlPullParser.END_DOCUMENT);
- success = true;
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "no current wallpaper -- first boot?");
- } catch (NullPointerException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (IOException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (IndexOutOfBoundsException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- }
- IoUtils.closeQuietly(stream);
-
- if (!success) {
- wallpaper.cropHint.set(0, 0, 0, 0);
- wpdData.mPadding.set(0, 0, 0, 0);
- wallpaper.name = "";
-
+ mWallpaperMap.put(userId, result.getSystemWallpaperData());
+ if (result.success()) {
+ mLockWallpaperMap.put(userId, result.getLockWallpaperData());
+ } else {
mLockWallpaperMap.remove(userId);
- } else {
- if (wallpaper.wallpaperId <= 0) {
- wallpaper.wallpaperId = makeWallpaperIdLocked();
- if (DEBUG) {
- Slog.w(TAG, "Didn't set wallpaper id in loadSettingsLocked(" + userId
- + "); now " + wallpaper.wallpaperId);
- }
- }
- }
-
- mWallpaperDisplayHelper.ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
- ensureSaneWallpaperData(wallpaper);
- WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
- if (lockWallpaper != null) {
- ensureSaneWallpaperData(lockWallpaper);
- lockWallpaper.mWhich = FLAG_LOCK;
- wallpaper.mWhich = FLAG_SYSTEM;
- } else {
- wallpaper.mWhich = FLAG_SYSTEM | FLAG_LOCK;
}
}
@@ -3766,84 +3498,6 @@
}
}
- private void ensureSaneWallpaperData(WallpaperData wallpaper) {
- // Only overwrite cropHint if the rectangle is invalid.
- if (wallpaper.cropHint.width() < 0
- || wallpaper.cropHint.height() < 0) {
- wallpaper.cropHint.set(0, 0, 0, 0);
- }
- }
-
- @VisibleForTesting
- void parseWallpaperAttributes(TypedXmlPullParser parser, WallpaperData wallpaper,
- boolean keepDimensionHints) throws XmlPullParserException {
- final int id = parser.getAttributeInt(null, "id", -1);
- if (id != -1) {
- wallpaper.wallpaperId = id;
- if (id > WallpaperUtils.getCurrentWallpaperId()) {
- WallpaperUtils.setCurrentWallpaperId(id);
- }
- } else {
- wallpaper.wallpaperId = makeWallpaperIdLocked();
- }
-
- final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
-
- if (!keepDimensionHints) {
- wpData.mWidth = parser.getAttributeInt(null, "width");
- wpData.mHeight = parser.getAttributeInt(null, "height");
- }
- wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
- wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
- wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
- wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
- wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
- wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
- wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
- wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
- wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
- int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0);
- if (dimAmountsCount > 0) {
- ArrayMap<Integer, Float> allDimAmounts = new ArrayMap<>(dimAmountsCount);
- for (int i = 0; i < dimAmountsCount; i++) {
- int uid = getAttributeInt(parser, "dimUID" + i, 0);
- float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f);
- allDimAmounts.put(uid, dimValue);
- }
- wallpaper.mUidToDimAmount = allDimAmounts;
- }
- int colorsCount = getAttributeInt(parser, "colorsCount", 0);
- int allColorsCount = getAttributeInt(parser, "allColorsCount", 0);
- if (allColorsCount > 0) {
- Map<Integer, Integer> allColors = new HashMap<>(allColorsCount);
- for (int i = 0; i < allColorsCount; i++) {
- int colorInt = getAttributeInt(parser, "allColorsValue" + i, 0);
- int population = getAttributeInt(parser, "allColorsPopulation" + i, 0);
- allColors.put(colorInt, population);
- }
- int colorHints = getAttributeInt(parser, "colorHints", 0);
- wallpaper.primaryColors = new WallpaperColors(allColors, colorHints);
- } else if (colorsCount > 0) {
- Color primary = null, secondary = null, tertiary = null;
- for (int i = 0; i < colorsCount; i++) {
- Color color = Color.valueOf(getAttributeInt(parser, "colorValue" + i, 0));
- if (i == 0) {
- primary = color;
- } else if (i == 1) {
- secondary = color;
- } else if (i == 2) {
- tertiary = color;
- } else {
- break;
- }
- }
- int colorHints = getAttributeInt(parser, "colorHints", 0);
- wallpaper.primaryColors = new WallpaperColors(primary, secondary, tertiary, colorHints);
- }
- wallpaper.name = parser.getAttributeValue(null, "name");
- wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
- }
-
// Called by SystemBackupAgent after files are restored to disk.
public void settingsRestored() {
// Verify caller is the system
@@ -3878,7 +3532,7 @@
success = true;
} else {
if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
- success = restoreNamedResourceLocked(wallpaper);
+ success = mWallpaperDataParser.restoreNamedResourceLocked(wallpaper);
}
if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
+ " id=" + wallpaper.wallpaperId);
@@ -3901,83 +3555,6 @@
}
}
- // Restore the named resource bitmap to both source + crop files
- private boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
- if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
- String resName = wallpaper.name.substring(4);
-
- String pkg = null;
- int colon = resName.indexOf(':');
- if (colon > 0) {
- pkg = resName.substring(0, colon);
- }
-
- String ident = null;
- int slash = resName.lastIndexOf('/');
- if (slash > 0) {
- ident = resName.substring(slash+1);
- }
-
- String type = null;
- if (colon > 0 && slash > 0 && (slash-colon) > 1) {
- type = resName.substring(colon+1, slash);
- }
-
- if (pkg != null && ident != null && type != null) {
- int resId = -1;
- InputStream res = null;
- FileOutputStream fos = null;
- FileOutputStream cos = null;
- try {
- Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
- Resources r = c.getResources();
- resId = r.getIdentifier(resName, null, null);
- if (resId == 0) {
- Slog.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
- + " ident=" + ident);
- return false;
- }
-
- res = r.openRawResource(resId);
- if (wallpaper.wallpaperFile.exists()) {
- wallpaper.wallpaperFile.delete();
- wallpaper.cropFile.delete();
- }
- fos = new FileOutputStream(wallpaper.wallpaperFile);
- cos = new FileOutputStream(wallpaper.cropFile);
-
- byte[] buffer = new byte[32768];
- int amt;
- while ((amt=res.read(buffer)) > 0) {
- fos.write(buffer, 0, amt);
- cos.write(buffer, 0, amt);
- }
- // mWallpaperObserver will notice the close and send the change broadcast
-
- Slog.v(TAG, "Restored wallpaper: " + resName);
- return true;
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "Package name " + pkg + " not found");
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Resource not found: " + resId);
- } catch (IOException e) {
- Slog.e(TAG, "IOException while restoring wallpaper ", e);
- } finally {
- IoUtils.closeQuietly(res);
- if (fos != null) {
- FileUtils.sync(fos);
- }
- if (cos != null) {
- FileUtils.sync(cos);
- }
- IoUtils.closeQuietly(fos);
- IoUtils.closeQuietly(cos);
- }
- }
- }
- return false;
- }
-
@Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -3986,6 +3563,56 @@
args, callback, resultReceiver);
}
+ private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) {
+ pw.print(" User "); pw.print(wallpaper.userId);
+ pw.print(": id="); pw.print(wallpaper.wallpaperId);
+ pw.print(": mWhich="); pw.print(wallpaper.mWhich);
+ pw.print(": mSystemWasBoth="); pw.println(wallpaper.mSystemWasBoth);
+ pw.println(" Display state:");
+ mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
+ pw.print(" displayId=");
+ pw.println(wpSize.mDisplayId);
+ pw.print(" mWidth=");
+ pw.print(wpSize.mWidth);
+ pw.print(" mHeight=");
+ pw.println(wpSize.mHeight);
+ pw.print(" mPadding="); pw.println(wpSize.mPadding);
+ });
+ pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
+ pw.print(" mName="); pw.println(wallpaper.name);
+ pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
+ pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
+ pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
+ pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
+ pw.println(" mUidToDimAmount:");
+ for (int j = 0; j < wallpaper.mUidToDimAmount.size(); j++) {
+ pw.print(" UID="); pw.print(wallpaper.mUidToDimAmount.keyAt(j));
+ pw.print(" dimAmount="); pw.println(wallpaper.mUidToDimAmount.valueAt(j));
+ }
+ if (wallpaper.connection != null) {
+ WallpaperConnection conn = wallpaper.connection;
+ pw.print(" Wallpaper connection ");
+ pw.print(conn);
+ pw.println(":");
+ if (conn.mInfo != null) {
+ pw.print(" mInfo.component=");
+ pw.println(conn.mInfo.getComponent());
+ }
+ conn.forEachDisplayConnector(connector -> {
+ pw.print(" mDisplayId=");
+ pw.println(connector.mDisplayId);
+ pw.print(" mToken=");
+ pw.println(connector.mToken);
+ pw.print(" mEngine=");
+ pw.println(connector.mEngine);
+ });
+ pw.print(" mService=");
+ pw.println(conn.mService);
+ pw.print(" mLastDiedTime=");
+ pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis());
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -3996,90 +3623,15 @@
synchronized (mLock) {
pw.println("System wallpaper state:");
for (int i = 0; i < mWallpaperMap.size(); i++) {
- WallpaperData wallpaper = mWallpaperMap.valueAt(i);
- pw.print(" User "); pw.print(wallpaper.userId);
- pw.print(": id="); pw.println(wallpaper.wallpaperId);
- pw.println(" Display state:");
- mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
- pw.print(" displayId=");
- pw.println(wpSize.mDisplayId);
- pw.print(" mWidth=");
- pw.print(wpSize.mWidth);
- pw.print(" mHeight=");
- pw.println(wpSize.mHeight);
- pw.print(" mPadding="); pw.println(wpSize.mPadding);
- });
- pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
- pw.print(" mName="); pw.println(wallpaper.name);
- pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
- pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
- pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
- pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
- pw.println(" mUidToDimAmount:");
- for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) {
- pw.print(" UID="); pw.print(entry.getKey());
- pw.print(" dimAmount="); pw.println(entry.getValue());
- }
- if (wallpaper.connection != null) {
- WallpaperConnection conn = wallpaper.connection;
- pw.print(" Wallpaper connection ");
- pw.print(conn);
- pw.println(":");
- if (conn.mInfo != null) {
- pw.print(" mInfo.component=");
- pw.println(conn.mInfo.getComponent());
- }
- conn.forEachDisplayConnector(connector -> {
- pw.print(" mDisplayId=");
- pw.println(connector.mDisplayId);
- pw.print(" mToken=");
- pw.println(connector.mToken);
- pw.print(" mEngine=");
- pw.println(connector.mEngine);
- });
- pw.print(" mService=");
- pw.println(conn.mService);
- pw.print(" mLastDiedTime=");
- pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis());
- }
+ dumpWallpaper(mWallpaperMap.valueAt(i), pw);
}
pw.println("Lock wallpaper state:");
for (int i = 0; i < mLockWallpaperMap.size(); i++) {
- WallpaperData wallpaper = mLockWallpaperMap.valueAt(i);
- pw.print(" User "); pw.print(wallpaper.userId);
- pw.print(": id="); pw.println(wallpaper.wallpaperId);
- pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
- pw.print(" mName="); pw.println(wallpaper.name);
- pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
- pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
+ dumpWallpaper(mLockWallpaperMap.valueAt(i), pw);
}
pw.println("Fallback wallpaper state:");
- pw.print(" User "); pw.print(mFallbackWallpaper.userId);
- pw.print(": id="); pw.println(mFallbackWallpaper.wallpaperId);
- pw.print(" mCropHint="); pw.println(mFallbackWallpaper.cropHint);
- pw.print(" mName="); pw.println(mFallbackWallpaper.name);
- pw.print(" mAllowBackup="); pw.println(mFallbackWallpaper.allowBackup);
- if (mFallbackWallpaper.connection != null) {
- WallpaperConnection conn = mFallbackWallpaper.connection;
- pw.print(" Fallback Wallpaper connection ");
- pw.print(conn);
- pw.println(":");
- if (conn.mInfo != null) {
- pw.print(" mInfo.component=");
- pw.println(conn.mInfo.getComponent());
- }
- conn.forEachDisplayConnector(connector -> {
- pw.print(" mDisplayId=");
- pw.println(connector.mDisplayId);
- pw.print(" mToken=");
- pw.println(connector.mToken);
- pw.print(" mEngine=");
- pw.println(connector.mEngine);
- });
- pw.print(" mService=");
- pw.println(conn.mService);
- pw.print(" mLastDiedTime=");
- pw.println(mFallbackWallpaper.lastDiedTime - SystemClock.uptimeMillis());
+ if (mFallbackWallpaper != null) {
+ dumpWallpaper(mFallbackWallpaper, pw);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2f82167..80f660e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4748,16 +4748,27 @@
updateResumedAppTrace(r);
mLastResumedActivity = r;
- final boolean changed = r.mDisplayContent.setFocusedApp(r);
- if (changed) {
- mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
- true /*updateInputWindows*/);
- }
- if (prevTask == null || task != prevTask) {
- if (prevTask != null) {
- mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
+ // Don't take focus when transient launching. We don't want the app to know anything
+ // until we've committed to the gesture. The focus will be transferred at the end of
+ // the transition (if the transient launch is committed) or early if explicitly requested
+ // via `setFocused*`.
+ if (!getTransitionController().isTransientCollect(r)) {
+ final Task prevFocusTask = r.mDisplayContent.mFocusedApp != null
+ ? r.mDisplayContent.mFocusedApp.getTask() : null;
+ final boolean changed = r.mDisplayContent.setFocusedApp(r);
+ if (changed) {
+ mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /*updateInputWindows*/);
}
- mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+ if (task != prevFocusTask) {
+ if (prevFocusTask != null) {
+ mTaskChangeNotificationController.notifyTaskFocusChanged(
+ prevFocusTask.mTaskId, false);
+ }
+ mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+ }
+ }
+ if (task != prevTask) {
mTaskSupervisor.mRecentTasks.add(task);
}
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 7d9a4ec..3f28522 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -24,6 +24,7 @@
import android.os.HandlerExecutor;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -48,9 +49,12 @@
private final int[] mRearDisplayDeviceStates;
@NonNull
private final int[] mReverseRotationAroundZAxisStates;
+ @GuardedBy("this")
@NonNull
private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
+ private final boolean mMatchBuiltInDisplayOrientationToDefaultDisplay;
+
@Nullable
private DeviceState mLastDeviceState;
private int mCurrentState;
@@ -72,20 +76,19 @@
.getIntArray(R.array.config_rearDisplayDeviceStates);
mReverseRotationAroundZAxisStates = context.getResources()
.getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
+ mMatchBuiltInDisplayOrientationToDefaultDisplay = context.getResources()
+ .getBoolean(R.bool
+ .config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay);
if (mDeviceStateManager != null) {
mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this);
}
}
- void unregisterFromDeviceStateManager() {
- if (mDeviceStateManager != null) {
- mDeviceStateManager.unregisterCallback(this);
- }
- }
-
void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
- mDeviceStateCallbacks.add(callback);
+ synchronized (this) {
+ mDeviceStateCallbacks.add(callback);
+ }
}
/**
@@ -95,6 +98,15 @@
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
+ /**
+ * @return true if non-default built-in displays should match the default display's rotation.
+ */
+ boolean shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay() {
+ // TODO(b/265991392): This should come from display_settings.xml once it's easier to
+ // extend with complex configurations.
+ return mMatchBuiltInDisplayOrientationToDefaultDisplay;
+ }
+
@Override
public void onStateChanged(int state) {
mCurrentState = state;
@@ -115,8 +127,10 @@
if (mLastDeviceState == null || !mLastDeviceState.equals(deviceState)) {
mLastDeviceState = deviceState;
- for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
- callback.accept(mLastDeviceState);
+ synchronized (this) {
+ for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
+ callback.accept(mLastDeviceState);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4174bfe..d93a62d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -595,7 +595,8 @@
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
- private final DeviceStateController mDeviceStateController;
+ @VisibleForTesting
+ final DeviceStateController mDeviceStateController;
private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
final RemoteDisplayChangeController mRemoteDisplayChangeController;
@@ -1091,7 +1092,8 @@
* @param display May not be null.
* @param root {@link RootWindowContainer}
*/
- DisplayContent(Display display, RootWindowContainer root) {
+ DisplayContent(Display display, RootWindowContainer root,
+ @NonNull DeviceStateController deviceStateController) {
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
if (mWmService.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
@@ -1151,11 +1153,11 @@
mWmService.mAtmService.getRecentTasks().getInputListener());
}
- mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH);
+ mDeviceStateController = deviceStateController;
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
- mDeviceStateController);
+ mDeviceStateController, root.getDisplayRotationCoordinator());
final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
(@NonNull DeviceStateController.DeviceState newFoldState) -> {
@@ -1730,7 +1732,7 @@
}
// The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
- if (handleTopActivityLaunchingInDifferentOrientation(
+ if (topCandidate != null && handleTopActivityLaunchingInDifferentOrientation(
topCandidate, r, true /* checkOpening */)) {
// Display orientation should be deferred until the top fixed rotation is finished.
return false;
@@ -2163,6 +2165,10 @@
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
}, true /* traverseTopToBottom */);
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
+ if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
+ // Make sure DisplayRotation#isRotatingSeamlessly() will return false.
+ mDisplayRotation.cancelSeamlessRotation();
+ }
}
mWmService.mDisplayManagerInternal.performTraversal(transaction);
@@ -3322,7 +3328,7 @@
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
mWmService.stopFreezingDisplayLocked();
- mDeviceStateController.unregisterFromDeviceStateManager();
+ mDisplayRotation.removeDefaultDisplayRotationChangedCallback();
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
@@ -3804,6 +3810,17 @@
*/
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
int topFocusedDisplayId) {
+ // Don't re-assign focus automatically away from a should-keep-focus app window.
+ // `findFocusedWindow` will always grab the transient-launch app since it is "on top" which
+ // would create a mismatch, so just early-out here.
+ if (mCurrentFocus != null && mTransitionController.shouldKeepFocus(mCurrentFocus)
+ // This is only keeping focus, so don't early-out if the focused-app has been
+ // explicitly changed (eg. via setFocusedTask).
+ && mFocusedApp != null && mCurrentFocus.isDescendantOf(mFocusedApp)
+ && mCurrentFocus.isVisible() && mCurrentFocus.isFocusable()) {
+ ProtoLog.v(WM_DEBUG_FOCUS, "Current transition prevents automatic focus change");
+ return false;
+ }
WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
if (mCurrentFocus == newFocus) {
return false;
@@ -4927,7 +4944,7 @@
mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
- if (!mWmService.mDisplayFrozen) {
+ if (!mWmService.mDisplayFrozen && !mDisplayRotation.isRotatingSeamlessly()) {
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3404279..7071aa7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -58,6 +58,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.util.RotationUtils;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -120,6 +121,11 @@
private FoldController mFoldController;
@NonNull
private final DeviceStateController mDeviceStateController;
+ @NonNull
+ private final DisplayRotationCoordinator mDisplayRotationCoordinator;
+ @NonNull
+ @VisibleForTesting
+ final Runnable mDefaultDisplayRotationChangedCallback;
@ScreenOrientation
private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -221,17 +227,19 @@
private boolean mDemoRotationLock;
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
- DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController) {
+ DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController,
+ @NonNull DisplayRotationCoordinator displayRotationCoordinator) {
this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock(),
- deviceStateController);
+ deviceStateController, displayRotationCoordinator);
}
@VisibleForTesting
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
DisplayAddress displayAddress, DisplayPolicy displayPolicy,
DisplayWindowSettings displayWindowSettings, Context context, Object lock,
- @NonNull DeviceStateController deviceStateController) {
+ @NonNull DeviceStateController deviceStateController,
+ @NonNull DisplayRotationCoordinator displayRotationCoordinator) {
mService = service;
mDisplayContent = displayContent;
mDisplayPolicy = displayPolicy;
@@ -252,6 +260,19 @@
int defaultRotation = readDefaultDisplayRotation(displayAddress);
mRotation = defaultRotation;
+ mDisplayRotationCoordinator = displayRotationCoordinator;
+ if (isDefaultDisplay) {
+ mDisplayRotationCoordinator.setDefaultDisplayDefaultRotation(mRotation);
+ }
+ mDefaultDisplayRotationChangedCallback = this::updateRotationAndSendNewConfigIfChanged;
+
+ if (DisplayRotationCoordinator.isSecondaryInternalDisplay(displayContent)
+ && mDeviceStateController
+ .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
+ mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
+ mDefaultDisplayRotationChangedCallback);
+ }
+
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
mOrientationListener =
@@ -494,8 +515,11 @@
return false;
}
+ @Surface.Rotation
final int oldRotation = mRotation;
+ @ScreenOrientation
final int lastOrientation = mLastOrientation;
+ @Surface.Rotation
int rotation = rotationForOrientation(lastOrientation, oldRotation);
// Use the saved rotation for tabletop mode, if set.
if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
@@ -507,6 +531,14 @@
Surface.rotationToString(oldRotation),
Surface.rotationToString(prevRotation));
}
+
+ if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)
+ && mDeviceStateController
+ .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
+ rotation = RotationUtils.reverseRotationDirectionAroundZAxis(
+ mDisplayRotationCoordinator.getDefaultDisplayCurrentRotation());
+ }
+
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
@@ -525,6 +557,10 @@
return false;
}
+ if (isDefaultDisplay) {
+ mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
+ }
+
// Preemptively cancel the running recents animation -- SysUI can't currently handle this
// case properly since the signals it receives all happen post-change. We do this earlier
// in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems
@@ -1142,17 +1178,12 @@
return mUserRotation;
}
+ @Surface.Rotation
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
- // Flipping 270 and 90 has the same effect as changing the direction which rotation is
- // applied.
- if (sensorRotation == Surface.ROTATION_90) {
- sensorRotation = Surface.ROTATION_270;
- } else if (sensorRotation == Surface.ROTATION_270) {
- sensorRotation = Surface.ROTATION_90;
- }
+ sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
if (sensorRotation < 0) {
@@ -1167,6 +1198,7 @@
final boolean deskDockEnablesAccelerometer =
mDisplayPolicy.isDeskDockEnablesAccelerometer();
+ @Surface.Rotation
final int preferredRotation;
if (!isDefaultDisplay) {
// For secondary displays we ignore things like displays sensors, docking mode and
@@ -1536,6 +1568,12 @@
return shouldUpdateRotation;
}
+ void removeDefaultDisplayRotationChangedCallback() {
+ if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
+ mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+ }
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "DisplayRotation");
pw.println(prefix + " mCurrentAppOrientation="
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
new file mode 100644
index 0000000..ae3787c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.Display;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Singleton for coordinating rotation across multiple displays. Used to notify non-default
+ * displays when the default display rotates.
+ *
+ * Note that this class does not need locking because it is always protected by WindowManagerService
+ * mGlobalLock.
+ */
+class DisplayRotationCoordinator {
+
+ private static final String TAG = "DisplayRotationCoordinator";
+
+ @Surface.Rotation
+ private int mDefaultDisplayDefaultRotation;
+
+ @Nullable
+ @VisibleForTesting
+ Runnable mDefaultDisplayRotationChangedCallback;
+
+ @Surface.Rotation
+ private int mDefaultDisplayCurrentRotation;
+
+ /**
+ * Notifies clients when the default display rotation changes.
+ */
+ void onDefaultDisplayRotationChanged(@Surface.Rotation int rotation) {
+ mDefaultDisplayCurrentRotation = rotation;
+
+ if (mDefaultDisplayRotationChangedCallback != null) {
+ mDefaultDisplayRotationChangedCallback.run();
+ }
+ }
+
+ void setDefaultDisplayDefaultRotation(@Surface.Rotation int rotation) {
+ mDefaultDisplayDefaultRotation = rotation;
+ }
+
+ @Surface.Rotation
+ int getDefaultDisplayCurrentRotation() {
+ return mDefaultDisplayCurrentRotation;
+ }
+
+ /**
+ * Register a callback to be notified when the default display's rotation changes. Clients can
+ * query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
+ */
+ void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+ if (mDefaultDisplayRotationChangedCallback != null) {
+ throw new UnsupportedOperationException("Multiple clients unsupported");
+ }
+
+ mDefaultDisplayRotationChangedCallback = callback;
+
+ if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
+ callback.run();
+ }
+ }
+
+ /**
+ * Removes the callback that was added via
+ * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+ */
+ void removeDefaultDisplayRotationChangedCallback() {
+ mDefaultDisplayRotationChangedCallback = null;
+ }
+
+ static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
+ if (displayContent.isDefaultDisplay) {
+ return false;
+ } else if (displayContent.mDisplay == null) {
+ return false;
+ }
+ return displayContent.mDisplay.getType() == Display.TYPE_INTERNAL;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 3e1105b..8c59548 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -405,8 +405,12 @@
// Apply recents input consumer when the focusing window is in recents animation.
final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null
&& recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord))
- // Shell transitions doesn't use RecentsAnimationController
- || getWeak(mActiveRecentsActivity) != null && focus.inTransition();
+ // Shell transitions doesn't use RecentsAnimationController but we still
+ // have carryover legacy logic that relies on the consumer.
+ || (getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+ // only take focus from the recents activity to avoid intercepting
+ // events before the gesture officially starts.
+ && focus.isActivityTypeHomeOrRecents());
if (shouldApplyRecentsInputConsumer) {
if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) {
requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index de42c55..f952adb 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -59,6 +59,8 @@
}
}
+ private final DisplayInfo mDisplayInfo;
+ private final Mode mDefaultMode;
private final Mode mLowRefreshRateMode;
private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -89,7 +91,9 @@
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
- mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
+ mDisplayInfo = displayInfo;
+ mDefaultMode = displayInfo.getDefaultMode();
+ mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -98,10 +102,9 @@
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
- private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
- Mode mode = displayInfo.getDefaultMode();
+ private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
float[] refreshRates = displayInfo.getDefaultRefreshRates();
- float bestRefreshRate = mode.getRefreshRate();
+ float bestRefreshRate = defaultMode.getRefreshRate();
mMinSupportedRefreshRate = bestRefreshRate;
mMaxSupportedRefreshRate = bestRefreshRate;
for (int i = refreshRates.length - 1; i >= 0; i--) {
@@ -127,13 +130,39 @@
}
int getPreferredModeId(WindowState w) {
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate.
- if (w.isAnimating(TRANSITION | PARENTS)) {
+ final int preferredDisplayModeId = w.mAttrs.preferredDisplayModeId;
+ if (preferredDisplayModeId <= 0) {
+ // Unspecified, use default mode.
return 0;
}
- return w.mAttrs.preferredDisplayModeId;
+ // If app is animating, it's not able to control refresh rate because we want the animation
+ // to run in default refresh rate. But if the display size of default mode is different
+ // from the using preferred mode, then still keep the preferred mode to avoid disturbing
+ // the animation.
+ if (w.isAnimating(TRANSITION | PARENTS)) {
+ Display.Mode preferredMode = null;
+ for (Display.Mode mode : mDisplayInfo.supportedModes) {
+ if (preferredDisplayModeId == mode.getModeId()) {
+ preferredMode = mode;
+ break;
+ }
+ }
+ if (preferredMode != null) {
+ final int pW = preferredMode.getPhysicalWidth();
+ final int pH = preferredMode.getPhysicalHeight();
+ if ((pW != mDefaultMode.getPhysicalWidth()
+ || pH != mDefaultMode.getPhysicalHeight())
+ && pW == mDisplayInfo.getNaturalWidth()
+ && pH == mDisplayInfo.getNaturalHeight()) {
+ // Prefer not to change display size when animating.
+ return preferredDisplayModeId;
+ }
+ }
+ return 0;
+ }
+
+ return preferredDisplayModeId;
}
/**
@@ -234,14 +263,10 @@
if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
final int preferredModeId = w.mAttrs.preferredDisplayModeId;
if (preferredModeId > 0) {
- DisplayInfo info = w.getDisplayInfo();
- if (info != null) {
- for (Display.Mode mode : info.supportedModes) {
- if (preferredModeId == mode.getModeId()) {
- return w.mFrameRateVote.update(mode.getRefreshRate(),
- Surface.FRAME_RATE_COMPATIBILITY_EXACT);
-
- }
+ for (Display.Mode mode : mDisplayInfo.supportedModes) {
+ if (preferredModeId == mode.getModeId()) {
+ return w.mFrameRateVote.update(mode.getRefreshRate(),
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e8aa2c8..2343906 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -136,6 +136,7 @@
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.TaskFragmentAnimationParams;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
@@ -234,6 +235,10 @@
WindowManagerService mWindowManager;
DisplayManager mDisplayManager;
private DisplayManagerInternal mDisplayManagerInternal;
+ @NonNull
+ private final DeviceStateController mDeviceStateController;
+ @NonNull
+ private final DisplayRotationCoordinator mDisplayRotationCoordinator;
/** Reference to default display so we can quickly look it up. */
private DisplayContent mDefaultDisplay;
@@ -440,6 +445,8 @@
mTaskSupervisor = mService.mTaskSupervisor;
mTaskSupervisor.mRootWindowContainer = this;
mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+ mDeviceStateController = new DeviceStateController(service.mContext, service.mH);
+ mDisplayRotationCoordinator = new DisplayRotationCoordinator();
}
/**
@@ -1279,7 +1286,8 @@
final Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
- final DisplayContent displayContent = new DisplayContent(display, this);
+ final DisplayContent displayContent =
+ new DisplayContent(display, this, mDeviceStateController);
addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
@@ -1297,6 +1305,10 @@
return mDefaultDisplay;
}
+ DisplayRotationCoordinator getDisplayRotationCoordinator() {
+ return mDisplayRotationCoordinator;
+ }
+
/**
* Get the default display area on the device dedicated to app windows. This one should be used
* only as a fallback location for activity launches when no target display area is specified,
@@ -1358,7 +1370,7 @@
return null;
}
// The display hasn't been added to ActivityManager yet, create a new record now.
- displayContent = new DisplayContent(display, this);
+ displayContent = new DisplayContent(display, this, mDeviceStateController);
addChild(displayContent, POSITION_BOTTOM);
return displayContent;
}
@@ -2082,6 +2094,8 @@
return;
}
tf.resetAdjacentTaskFragment();
+ tf.setCompanionTaskFragment(null /* companionTaskFragment */);
+ tf.setAnimationParams(TaskFragmentAnimationParams.DEFAULT);
if (tf.getTopNonFinishingActivity() != null) {
// When the Task is entering picture-in-picture, we should clear all override
// from the client organizer, so the PIP activity can get the correct config
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index eb06b91..294e90b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2813,7 +2813,7 @@
void removeImmediately() {
mIsRemovalRequested = false;
resetAdjacentTaskFragment();
- cleanUp();
+ cleanUpEmbeddedTaskFragment();
final boolean shouldExecuteAppTransition =
mClearedTaskFragmentForPip && isTaskVisibleRequested();
super.removeImmediately();
@@ -2830,10 +2830,20 @@
}
/** Called on remove to cleanup. */
- private void cleanUp() {
- if (mIsEmbedded) {
- mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
+ private void cleanUpEmbeddedTaskFragment() {
+ if (!mIsEmbedded) {
+ return;
}
+ mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
+ final Task task = getTask();
+ if (task == null) {
+ return;
+ }
+ task.forAllLeafTaskFragments(taskFragment -> {
+ if (taskFragment.getCompanionTaskFragment() == this) {
+ taskFragment.setCompanionTaskFragment(null /* companionTaskFragment */);
+ }
+ }, false /* traverseTopToBottom */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d2f30ce..04a2761 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -216,6 +216,12 @@
final TransitionController.Logger mLogger = new TransitionController.Logger();
+ /**
+ * {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware
+ * of it). Currently, this happens before the display is ready since nothing can be seen yet.
+ */
+ boolean mIsPlayerEnabled = true;
+
Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
@@ -277,6 +283,17 @@
return false;
}
+ /** @return whether `wc` is a descendent of a transient-hide window. */
+ boolean isInTransientHide(@NonNull WindowContainer wc) {
+ if (mTransientLaunches == null) return false;
+ for (int i = 0; i < mTransientLaunches.size(); ++i) {
+ if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean isTransientLaunch(@NonNull ActivityRecord activity) {
return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
}
@@ -766,7 +783,7 @@
* be called directly; use {@link TransitionController#finishTransition} instead.
*/
void finishTransition() {
- if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
}
@@ -825,6 +842,11 @@
&& ar.isVisible()) {
// Transient launch was committed, so report enteringAnimation
ar.mEnteringAnimation = true;
+ // Since transient launches don't automatically take focus, make sure we
+ // synchronize focus since we committed to the launch.
+ if (ar.isTopRunningActivity()) {
+ ar.moveFocusableActivityToTop("transitionFinished");
+ }
}
continue;
}
@@ -1096,7 +1118,7 @@
controller.setupStartTransaction(transaction);
}
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
- if (mController.getTransitionPlayer() != null) {
+ if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
@@ -1112,11 +1134,16 @@
} catch (RemoteException e) {
// If there's an exception when trying to send the mergedTransaction to the
// client, we should finish and apply it here so the transactions aren't lost.
- cleanUpOnFailure();
+ postCleanupOnFailure();
}
} else {
- // No player registered, so just finish/apply immediately
- cleanUpOnFailure();
+ // No player registered or it's not enabled, so just finish/apply immediately
+ if (!mIsPlayerEnabled) {
+ mLogger.mSendTimeNs = SystemClock.uptimeNanos();
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately"
+ + " because player is disabled for transition #%d .", mSyncId);
+ }
+ postCleanupOnFailure();
}
mController.mLoggerHandler.post(mLogger::logOnSend);
mOverrideOptions = null;
@@ -1127,6 +1154,14 @@
info.releaseAnimSurfaces();
}
+ private void postCleanupOnFailure() {
+ mController.mAtm.mH.post(() -> {
+ synchronized (mController.mAtm.mGlobalLock) {
+ cleanUpOnFailure();
+ }
+ });
+ }
+
/**
* If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
* this directly, it's designed to by called by {@link TransitionController} only.
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 5e116ba..2c23b5d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -126,6 +126,13 @@
final Handler mLoggerHandler = FgThread.getHandler();
+ /**
+ * {@code true} While this waits for the display to become enabled (during boot). While waiting
+ * for the display, all core-initiated transitions will be "local".
+ * Note: This defaults to false so that it doesn't interfere with unit tests.
+ */
+ boolean mIsWaitingForDisplayEnabled = false;
+
TransitionController(ActivityTaskManagerService atm,
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
@@ -353,11 +360,37 @@
}
/**
+ * During transient-launch, the "behind" app should retain focus during the transition unless
+ * something takes focus from it explicitly (eg. by calling ATMS.setFocusedTask or by another
+ * transition interrupting this one.
+ *
+ * The rules around this are basically: if there is exactly one active transition and `wc` is
+ * the "behind" of a transient launch, then it can retain focus.
+ */
+ boolean shouldKeepFocus(@NonNull WindowContainer wc) {
+ if (mCollectingTransition != null) {
+ if (!mPlayingTransitions.isEmpty()) return false;
+ return mCollectingTransition.isInTransientHide(wc);
+ } else if (mPlayingTransitions.size() == 1) {
+ return mPlayingTransitions.get(0).isInTransientHide(wc);
+ }
+ return false;
+ }
+
+ /**
+ * @return {@code true} if {@param ar} is part of a transient-launch activity in the
+ * collecting transition.
+ */
+ boolean isTransientCollect(@NonNull ActivityRecord ar) {
+ return mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar);
+ }
+
+ /**
* @return {@code true} if {@param ar} is part of a transient-launch activity in an active
* transition.
*/
boolean isTransientLaunch(@NonNull ActivityRecord ar) {
- if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) {
+ if (isTransientCollect(ar)) {
return true;
}
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
@@ -460,6 +493,15 @@
Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange) {
+ if (mIsWaitingForDisplayEnabled) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Disabling player for transition"
+ + " #%d because display isn't enabled yet", transition.getSyncId());
+ transition.mIsPlayerEnabled = false;
+ transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos();
+ mAtm.mH.post(() -> mAtm.mWindowOrganizerController.startTransition(
+ transition.getToken(), null));
+ return transition;
+ }
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b46a720..06a8869 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,7 @@
import com.android.internal.policy.IKeyguardLockedStateListener;
import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.DumpUtils;
@@ -3776,6 +3777,12 @@
// Make sure the last requested orientation has been applied.
updateRotationUnchecked(false, false);
+
+ synchronized (mGlobalLock) {
+ mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Notified TransitionController "
+ + "that the display is ready.");
+ }
}
private boolean checkBootAnimationCompleteLocked() {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index abdc708..73e417e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -168,6 +168,7 @@
void setWindowManager(WindowManagerService wms) {
mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController,
wms.mTransitionTracer);
+ mTransitionController.mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled;
mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
}
@@ -431,6 +432,7 @@
applyTransaction(wct, -1 /* syncId */, transition, caller);
mTransitionController.requestStartTransition(transition, null /* startTask */,
null /* remoteTransition */, null /* displayChange */);
+ transition.setAllReady();
return;
}
@@ -469,6 +471,7 @@
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
null /* displayChange */);
+ nextTransition.setAllReady();
} else {
nextTransition.abort();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 703cb8a4..7b880fe 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3160,15 +3160,25 @@
+ (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
+ " canReceiveTouchInput=" + canReceiveTouchInput()
+ " displayIsOnTop=" + getDisplayContent().isOnTop()
- + " displayIsTrusted=" + getDisplayContent().isTrusted();
+ + " displayIsTrusted=" + getDisplayContent().isTrusted()
+ + " transitShouldKeepFocus=" + (mActivityRecord != null
+ && mTransitionController.shouldKeepFocus(mActivityRecord));
}
public boolean canReceiveKeys(boolean fromUserTouch) {
+ if (mActivityRecord != null && mTransitionController.shouldKeepFocus(mActivityRecord)) {
+ // During transient launch, the transient-hide windows are not visibleRequested
+ // or on-top but are kept focusable and thus can receive keys.
+ return true;
+ }
final boolean canReceiveKeys = isVisibleRequestedOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
- && canReceiveTouchInput();
+ // can it receive touches
+ && (mActivityRecord == null || mActivityRecord.getTask() == null
+ || !mActivityRecord.getTask().getRootTask().shouldIgnoreInput());
+
if (!canReceiveKeys) {
return false;
}
@@ -3194,6 +3204,11 @@
if (mActivityRecord == null || mActivityRecord.getTask() == null) {
return true;
}
+ // During transient launch, the transient-hide windows are not visibleRequested
+ // or on-top but are kept focusable and thus can receive touch input.
+ if (mTransitionController.shouldKeepFocus(mActivityRecord)) {
+ return true;
+ }
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.isVisibleRequested();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index f4d1d1e..b4e2fb6 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -137,6 +137,7 @@
jmethodID notifyDropWindow;
jmethodID getParentSurfaceForPointers;
jmethodID isPerDisplayTouchModeEnabled;
+ jmethodID isStylusPointerIconEnabled;
} gServiceClassInfo;
static struct {
@@ -363,6 +364,7 @@
std::map<PointerIconStyle, SpriteIcon>* outResources,
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId);
virtual PointerIconStyle getDefaultPointerIconId();
+ virtual PointerIconStyle getDefaultStylusIconId();
virtual PointerIconStyle getCustomPointerIconId();
virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
@@ -659,6 +661,12 @@
outConfig->pointerGestureTapSlop = hoverTapSlop;
}
+ jboolean stylusPointerIconEnabled =
+ env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isStylusPointerIconEnabled);
+ if (!checkAndClearExceptionFromCallback(env, "isStylusPointerIconEnabled")) {
+ outConfig->stylusPointerIconEnabled = stylusPointerIconEnabled;
+ }
+
{ // acquire lock
AutoMutex _l(mLock);
@@ -1605,6 +1613,11 @@
return PointerIconStyle::TYPE_ARROW;
}
+PointerIconStyle NativeInputManager::getDefaultStylusIconId() {
+ // TODO: add resource for default stylus icon and change this
+ return PointerIconStyle::TYPE_CROSSHAIR;
+}
+
PointerIconStyle NativeInputManager::getCustomPointerIconId() {
return PointerIconStyle::TYPE_CUSTOM;
}
@@ -2787,6 +2800,9 @@
GET_METHOD_ID(gServiceClassInfo.isPerDisplayTouchModeEnabled, clazz,
"isPerDisplayTouchModeEnabled", "()Z");
+ GET_METHOD_ID(gServiceClassInfo.isStylusPointerIconEnabled, clazz, "isStylusPointerIconEnabled",
+ "()Z");
+
// InputDevice
FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
deleted file mode 100644
index 3d71739..0000000
--- a/services/java/com/android/server/BootUserInitializer.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import com.android.server.am.ActivityManagerService;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.utils.Slogf;
-import com.android.server.utils.TimingsTraceAndSlog;
-
-/**
- * Class responsible for booting the device in the proper user on headless system user mode.
- *
- */
-// TODO(b/204091126): STOPSHIP - provide proper APIs
-final class BootUserInitializer {
-
- private static final String TAG = BootUserInitializer.class.getSimpleName();
-
- // TODO(b/204091126): STOPSHIP - set to false or dynamic value
- private static final boolean DEBUG = true;
-
- private final ActivityManagerService mAms;
- private final ContentResolver mContentResolver;
-
- BootUserInitializer(ActivityManagerService am, ContentResolver contentResolver) {
- mAms = am;
- mContentResolver = contentResolver;
- }
-
- public void init(TimingsTraceAndSlog t) {
- Slogf.i(TAG, "init())");
-
- // TODO(b/204091126): in the long term, we need to decide who's reponsible for that,
- // this class or the setup wizard app
- provisionHeadlessSystemUser();
-
- unlockSystemUser(t);
-
- try {
- t.traceBegin("getBootUser");
- int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser();
- t.traceEnd();
- t.traceBegin("switchToBootUser-" + bootUser);
- switchToBootUser(bootUser);
- t.traceEnd();
- } catch (UserManager.CheckedUserOperationException e) {
- Slogf.wtf(TAG, "Failed to created boot user", e);
- }
- }
-
- /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
- private void provisionHeadlessSystemUser() {
- if (isDeviceProvisioned()) {
- Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned");
- return;
- }
-
- Slogf.i(TAG, "Marking USER_SETUP_COMPLETE for system user");
- Settings.Secure.putInt(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1);
- Slogf.i(TAG, "Marking DEVICE_PROVISIONED for system user");
- Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 1);
- }
-
- private boolean isDeviceProvisioned() {
- try {
- return Settings.Global.getInt(mContentResolver,
- Settings.Global.DEVICE_PROVISIONED) == 1;
- } catch (Exception e) {
- Slogf.wtf(TAG, "DEVICE_PROVISIONED setting not found.", e);
- return false;
- }
- }
-
- // NOTE: Mostly copied from Automotive's InitialUserSetter
- private void unlockSystemUser(TimingsTraceAndSlog t) {
- Slogf.i(TAG, "Unlocking system user");
- t.traceBegin("unlock-system-user");
- try {
- // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
- // update the state and USER_SYSTEM unlock happens twice.
- t.traceBegin("am.startUser");
- boolean started = mAms.startUserInBackgroundWithListener(UserHandle.USER_SYSTEM,
- /* listener= */ null);
- t.traceEnd();
- if (!started) {
- Slogf.w(TAG, "could not restart system user in background; trying unlock instead");
- t.traceBegin("am.unlockUser");
- boolean unlocked = mAms.unlockUser(UserHandle.USER_SYSTEM, /* token= */ null,
- /* secret= */ null, /* listener= */ null);
- t.traceEnd();
- if (!unlocked) {
- Slogf.w(TAG, "could not unlock system user either");
- return;
- }
- }
- } finally {
- t.traceEnd();
- }
- }
-
- private void switchToBootUser(@UserIdInt int bootUserId) {
- Slogf.i(TAG, "Switching to boot user %d", bootUserId);
- boolean started = mAms.startUserInForegroundWithListener(bootUserId,
- /* unlockListener= */ null);
- if (!started) {
- Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
- }
- }
-}
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
new file mode 100644
index 0000000..c4ad80e
--- /dev/null
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.Slogf;
+import com.android.server.utils.TimingsTraceAndSlog;
+
+/**
+ * Class responsible for booting the device in the proper user on headless system user mode.
+ *
+ */
+final class HsumBootUserInitializer {
+
+ private static final String TAG = HsumBootUserInitializer.class.getSimpleName();
+
+ private final UserManagerInternal mUmi;
+ private final ActivityManagerService mAms;
+ private final ContentResolver mContentResolver;
+
+ private final ContentObserver mDeviceProvisionedObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ // Set USER_SETUP_COMPLETE for the (headless) system user only when the device
+ // has been set up at least once.
+ if (isDeviceProvisioned()) {
+ Slogf.i(TAG, "Marking USER_SETUP_COMPLETE for system user");
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.USER_SETUP_COMPLETE, 1);
+ mContentResolver.unregisterContentObserver(mDeviceProvisionedObserver);
+ }
+ }
+ };
+
+ /** Whether this device should always have a non-removable MainUser, including at first boot. */
+ private final boolean mShouldAlwaysHaveMainUser;
+
+ /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
+ public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
+ ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ return null;
+ }
+ return new HsumBootUserInitializer(
+ LocalServices.getService(UserManagerInternal.class),
+ am, contentResolver, shouldAlwaysHaveMainUser);
+ }
+
+ private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
+ ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+ mUmi = umi;
+ mAms = am;
+ mContentResolver = contentResolver;
+ this.mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
+ }
+
+ /**
+ * Initialize this object, and create MainUser if needed.
+ *
+ * Should be called before PHASE_SYSTEM_SERVICES_READY as services' setups may require MainUser,
+ * but probably after PHASE_LOCK_SETTINGS_READY since that may be needed for user creation.
+ */
+ public void init(TimingsTraceAndSlog t) {
+ Slogf.i(TAG, "init())");
+
+ if (mShouldAlwaysHaveMainUser) {
+ t.traceBegin("createMainUserIfNeeded");
+ createMainUserIfNeeded();
+ t.traceEnd();
+ }
+ }
+
+ private void createMainUserIfNeeded() {
+ int mainUser = mUmi.getMainUserId();
+ if (mainUser != UserHandle.USER_NULL) {
+ Slogf.d(TAG, "Found existing MainUser, userId=%d", mainUser);
+ return;
+ }
+
+ Slogf.d(TAG, "Creating a new MainUser");
+ try {
+ final UserInfo newInitialUser = mUmi.createUserEvenWhenDisallowed(
+ /* name= */ null, // null will appear as "Owner" in on-demand localisation
+ UserManager.USER_TYPE_FULL_SECONDARY,
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN,
+ /* disallowedPackages= */ null,
+ /* token= */ null);
+ if (newInitialUser == null) {
+ Slogf.wtf(TAG, "Initial bootable MainUser creation failed: returned null");
+ } else {
+ Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id);
+ }
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.wtf(TAG, "Initial bootable MainUser creation failed", e);
+ }
+ }
+
+ /**
+ * Put the device into the correct user state: unlock the system and switch to the boot user.
+ *
+ * Should only call once PHASE_THIRD_PARTY_APPS_CAN_START is reached to ensure that privileged
+ * apps have had the chance to set the boot user, if applicable.
+ */
+ public void systemRunning(TimingsTraceAndSlog t) {
+ observeDeviceProvisioning();
+ unlockSystemUser(t);
+
+ try {
+ t.traceBegin("getBootUser");
+ final int bootUser = mUmi.getBootUser();
+ t.traceEnd();
+ t.traceBegin("switchToBootUser-" + bootUser);
+ switchToBootUser(bootUser);
+ t.traceEnd();
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.wtf(TAG, "Failed to switch to boot user since there isn't one.");
+ }
+ }
+
+ private void observeDeviceProvisioning() {
+ if (isDeviceProvisioned()) {
+ return;
+ }
+
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false,
+ mDeviceProvisionedObserver
+ );
+ }
+
+ private boolean isDeviceProvisioned() {
+ try {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.DEVICE_PROVISIONED) == 1;
+ } catch (Exception e) {
+ Slogf.wtf(TAG, "DEVICE_PROVISIONED setting not found.", e);
+ return false;
+ }
+ }
+
+ // NOTE: Mostly copied from Automotive's InitialUserSetter
+ private void unlockSystemUser(TimingsTraceAndSlog t) {
+ Slogf.i(TAG, "Unlocking system user");
+ t.traceBegin("unlock-system-user");
+ try {
+ // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
+ // update the state and USER_SYSTEM unlock happens twice.
+ t.traceBegin("am.startUser");
+ boolean started = mAms.startUserInBackgroundWithListener(UserHandle.USER_SYSTEM,
+ /* listener= */ null);
+ t.traceEnd();
+ if (!started) {
+ Slogf.w(TAG, "could not restart system user in background; trying unlock instead");
+ t.traceBegin("am.unlockUser");
+ boolean unlocked = mAms.unlockUser(UserHandle.USER_SYSTEM, /* token= */ null,
+ /* secret= */ null, /* listener= */ null);
+ t.traceEnd();
+ if (!unlocked) {
+ Slogf.w(TAG, "could not unlock system user either");
+ return;
+ }
+ }
+ } finally {
+ t.traceEnd();
+ }
+ }
+
+ private void switchToBootUser(@UserIdInt int bootUserId) {
+ Slogf.i(TAG, "Switching to boot user %d", bootUserId);
+ boolean started = mAms.startUserInForegroundWithListener(bootUserId,
+ /* unlockListener= */ null);
+ if (!started) {
+ Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a15c6d2..d22be9e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -75,7 +75,6 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -2694,6 +2693,18 @@
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_LOCK_SETTINGS_READY);
t.traceEnd();
+ // Create initial user if needed, which should be done early since some system services rely
+ // on it in their setup, but likely needs to be done after LockSettingsService is ready.
+ final HsumBootUserInitializer hsumBootUserInitializer =
+ HsumBootUserInitializer.createInstance(
+ mActivityManagerService, mContentResolver,
+ context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
+ if (hsumBootUserInitializer != null) {
+ t.traceBegin("HsumBootUserInitializer.init");
+ hsumBootUserInitializer.init(t);
+ t.traceEnd();
+ }
+
t.traceBegin("StartBootPhaseSystemServicesReady");
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_SYSTEM_SERVICES_READY);
t.traceEnd();
@@ -2961,10 +2972,10 @@
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
t.traceEnd();
- if (UserManager.isHeadlessSystemUserMode() && !isAutomotive) {
- // TODO(b/204091126): remove isAutomotive check once the workflow is finalized
- t.traceBegin("BootUserInitializer");
- new BootUserInitializer(mActivityManagerService, mContentResolver).init(t);
+ if (hsumBootUserInitializer != null && !isAutomotive) {
+ // TODO(b/261924826): remove isAutomotive check once the workflow is finalized
+ t.traceBegin("HsumBootUserInitializer.systemRunning");
+ hsumBootUserInitializer.systemRunning(t);
t.traceEnd();
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index a9884dd..02da25d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -782,6 +782,7 @@
}
}
} else {
+ val wasGrantedByLegacy = newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)
newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
val isLeanbackNotificationsPermission = newState.systemState.isLeanback &&
@@ -805,10 +806,16 @@
} else {
newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
}
+ if ((wasGrantedByLegacy || wasGrantedByImplicit) && !shouldGrantByImplicit) {
+ // The permission was granted from a compatibility grant or an implicit grant,
+ // however this flag might still be set if the user denied this permission in
+ // the settings. Hence upon app upgrade and when this permission is no longer
+ // LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke the permission, we want to
+ // remove this flag so that the app can request the permission again.
+ newFlags = newFlags andInv PermissionFlags.APP_OP_REVOKED
+ }
val hasImplicitFlag = newFlags.hasBits(PermissionFlags.IMPLICIT)
if (!isImplicitPermission && hasImplicitFlag) {
- // TODO: We might not want to remove the IMPLICIT flag
- // for NOTIFICATION_PERMISSIONS
newFlags = newFlags andInv PermissionFlags.IMPLICIT
var shouldRetainAsNearbyDevices = false
if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
@@ -994,11 +1001,9 @@
permissionName: String
): Boolean? {
val permissionAllowlist = newState.systemState.permissionAllowlist
- // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName.
- // val apexModuleName = androidPackage.apexModuleName
val apexModuleName = permissionAllowlist.apexPrivilegedAppAllowlists
.firstNotNullOfOrNullIndexed { _, apexModuleName, apexAllowlist ->
- if (packageState.packageName in apexAllowlist) apexModuleName else null
+ if (packageState.apexModuleName in apexAllowlist) apexModuleName else null
}
val packageName = packageState.packageName
return when {
@@ -1190,9 +1195,7 @@
// Special permission for the recents app.
return true
}
- // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName.
- // This should be androidPackage.apexModuleName instead
- if (permission.isModule && androidPackage.packageName != null) {
+ if (permission.isModule && packageState.apexModuleName != null) {
// Special permission granted for APKs inside APEX modules.
return true
}
@@ -1397,11 +1400,11 @@
Manifest.permission.READ_MEDIA_VIDEO,
)
- // TODO: also add the permission NEARBY_WIFI_DEVICES to this set
private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT,
- Manifest.permission.BLUETOOTH_SCAN
+ Manifest.permission.BLUETOOTH_SCAN,
+ Manifest.permission.NEARBY_WIFI_DEVICES
)
private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 1abcf38..bfbc0f5 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -267,7 +267,7 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
mBackupEligibilityRules = new BackupEligibilityRules(mPackageManager,
- LocalServices.getService(PackageManagerInternal.class), USER_ID,
+ LocalServices.getService(PackageManagerInternal.class), USER_ID, mContext,
BACKUP_DESTINATION);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 9f7b72c..fb05699 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -17,7 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
-import static android.app.ActivityManager.PROCESS_CAPABILITY_NETWORK;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -38,6 +38,7 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -226,6 +227,15 @@
}
}
+ private static void assertBfsl(ProcessRecord app) {
+ assertEquals(PROCESS_CAPABILITY_BFSL,
+ app.mState.getSetCapability() & PROCESS_CAPABILITY_BFSL);
+ }
+
+ private static void assertNoBfsl(ProcessRecord app) {
+ assertEquals(0, app.mState.getSetCapability() & PROCESS_CAPABILITY_BFSL);
+ }
+
/**
* Replace the process LRU with the given processes.
* @param apps
@@ -264,6 +274,7 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
SCHED_GROUP_RESTRICTED);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -335,6 +346,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, FOREGROUND_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -447,6 +459,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -460,6 +473,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -471,7 +485,7 @@
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
s.startRequested = true;
s.isForeground = true;
- s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
s.setShortFgsInfo(SystemClock.uptimeMillis());
// SHORT_SERVICE FGS will get IMP_FG and a slightly different recent-adjustment.
@@ -480,16 +494,15 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.startService(s);
app.mServices.setHasForegroundServices(true,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
+ assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
- // Should get network access.
- assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) != 0);
+ assertNoBfsl(app);
}
// SHORT_SERVICE, but no longer recent.
@@ -497,7 +510,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasForegroundServices(true,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
app.mServices.startService(s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
@@ -505,17 +518,16 @@
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
+ assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
PERCEPTIBLE_MEDIUM_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
- // Still should get network access.
- assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) != 0);
+ assertNoBfsl(app);
}
// SHORT_SERVICE, timed out already.
s = ServiceRecord.newEmptyInstanceForTest(sService);
s.startRequested = true;
s.isForeground = true;
- s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
s.setShortFgsInfo(SystemClock.uptimeMillis()
- sService.mConstants.mShortFgsTimeoutDuration
- sService.mConstants.mShortFgsProcStateExtraWaitDuration);
@@ -523,7 +535,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasForegroundServices(true,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
app.mServices.startService(s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
@@ -533,9 +545,7 @@
// Procstate should be lower than FGS. (It should be SERVICE)
assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
-
- // Shouldn't have the network capability now.
- assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) == 0);
+ assertNoBfsl(app);
}
}
@@ -564,6 +574,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -925,6 +936,7 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -940,6 +952,7 @@
updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ assertNoBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -973,6 +986,8 @@
assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
assertEquals(PROCESS_STATE_PERSISTENT, client.mState.getSetProcState());
+ assertBfsl(client);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -988,6 +1003,7 @@
updateOomAdj(app);
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
+ assertNoBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1002,7 +1018,92 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
+ assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, client.mState.getSetProcState());
+ assertBfsl(client);
assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
+ assertBfsl(app);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_Service_ImportantFgService_ShortFgs() {
+ // Client has a SHORT_SERVICE FGS, which isn't allowed BFSL.
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ bindService(app, client, null, 0, mock(IBinder.class));
+
+ // In order to trick OomAdjuster to think it has a short-service, we need this logic.
+ ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+ s.startRequested = true;
+ s.isForeground = true;
+ s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.setShortFgsInfo(SystemClock.uptimeMillis());
+ client.mServices.startService(s);
+ client.mState.setLastTopTime(SystemClock.uptimeMillis());
+
+ client.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+ /* hasNoneType=*/false);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app);
+
+ // Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
+ assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, client.mState.getSetProcState());
+ assertNoBfsl(client);
+ assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
+ assertNoBfsl(app);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_Service_BoundForegroundService_with_ShortFgs() {
+
+ // app2, which is bound by app1 (which makes it BFGS)
+ // but it also has a short-fgs.
+ ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+
+ // In order to trick OomAdjuster to think it has a short-service, we need this logic.
+ ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+ s.startRequested = true;
+ s.isForeground = true;
+ s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.setShortFgsInfo(SystemClock.uptimeMillis());
+ app2.mServices.startService(s);
+ app2.mState.setLastTopTime(SystemClock.uptimeMillis());
+
+ app2.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+ /* hasNoneType=*/false);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(app2);
+
+ // Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
+ assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app2.mState.getSetProcState());
+ assertNoBfsl(app2);
+
+ // Now, create a BFGS process (app1), and make it bind to app 2
+
+ // Persistent process
+ ProcessRecord pers = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ pers.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+
+ // app1, which is bound by pers (which makes it BFGS)
+ ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+
+ bindService(app1, pers, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
+ bindService(app2, app1, null, 0, mock(IBinder.class));
+
+ updateOomAdj(pers, app1, app2);
+
+ assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app1.mState.getSetProcState());
+ assertBfsl(app1);
+
+ // Now, app2 gets BFSL from app1.
+ assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app2.mState.getSetProcState());
+ assertBfsl(app2);
}
@SuppressWarnings("GuardedBy")
@@ -1022,11 +1123,13 @@
doReturn(null).when(sService.mBackupTargets).get(anyInt());
assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
+ assertNoBfsl(app);
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
updateOomAdj(app);
assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1172,6 +1275,7 @@
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
+ assertNoBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1231,6 +1335,42 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_Provider_FgService_ShortFgs() {
+ // Client has a SHORT_SERVICE FGS, which isn't allowed BFSL.
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+
+ // In order to trick OomAdjuster to think it has a short-service, we need this logic.
+ ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+ s.startRequested = true;
+ s.isForeground = true;
+ s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.setShortFgsInfo(SystemClock.uptimeMillis());
+ client.mServices.startService(s);
+ client.mState.setLastTopTime(SystemClock.uptimeMillis());
+
+ client.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+ /* hasNoneType=*/false);
+ bindProvider(app, client, null, null, false);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app);
+
+ // Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
+ assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1,
+ SCHED_GROUP_DEFAULT);
+ assertNoBfsl(client);
+ assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1,
+ SCHED_GROUP_DEFAULT);
+ assertNoBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1299,6 +1439,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1318,6 +1459,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1346,6 +1488,9 @@
SCHED_GROUP_DEFAULT);
assertProcStates(client2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(client);
+ assertBfsl(client2);
client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -1354,6 +1499,9 @@
assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
+ assertNoBfsl(app);
+ assertNoBfsl(client);
+ assertNoBfsl(client2);
}
@SuppressWarnings("GuardedBy")
@@ -1378,6 +1526,9 @@
SCHED_GROUP_DEFAULT);
assertProcStates(client2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(client);
+ assertBfsl(client2);
}
@SuppressWarnings("GuardedBy")
@@ -1402,6 +1553,9 @@
SCHED_GROUP_DEFAULT);
assertProcStates(client2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(client);
+ assertBfsl(client2);
}
@SuppressWarnings("GuardedBy")
@@ -1437,6 +1591,11 @@
SCHED_GROUP_DEFAULT);
assertProcStates(client4, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(client);
+ assertBfsl(client2);
+ assertBfsl(client3);
+ assertBfsl(client4);
}
@SuppressWarnings("GuardedBy")
@@ -1461,6 +1620,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1542,6 +1702,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1567,6 +1728,7 @@
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1586,6 +1748,7 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1606,6 +1769,7 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1625,6 +1789,7 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1645,6 +1810,7 @@
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
}
@SuppressWarnings("GuardedBy")
@@ -1672,6 +1838,8 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app1);
+ assertBfsl(app2);
bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
@@ -1688,6 +1856,7 @@
SCHED_GROUP_TOP_APP);
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app2);
}
@SuppressWarnings("GuardedBy")
@@ -1770,6 +1939,7 @@
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app1);
}
@SuppressWarnings("GuardedBy")
@@ -1790,6 +1960,7 @@
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app1);
}
@SuppressWarnings("GuardedBy")
@@ -1912,6 +2083,7 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app2);
}
@SuppressWarnings("GuardedBy")
@@ -1931,6 +2103,8 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(app2);
}
@SuppressWarnings("GuardedBy")
@@ -1964,6 +2138,9 @@
assertEquals(false, app.mState.isEmpty());
assertEquals(false, app2.mState.isEmpty());
assertEquals(false, app3.mState.isEmpty());
+ assertBfsl(app);
+ assertBfsl(app2);
+ assertBfsl(app3);
}
@SuppressWarnings("GuardedBy")
@@ -2001,6 +2178,11 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app5, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(app2);
+ assertBfsl(app3);
+ // 4 is IMP_FG
+ assertBfsl(app5);
}
@SuppressWarnings("GuardedBy")
@@ -2038,6 +2220,11 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app5, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(app2);
+ assertBfsl(app3);
+ // 4 is IMP_FG
+ assertBfsl(app5);
}
@SuppressWarnings("GuardedBy")
@@ -2075,6 +2262,11 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app5, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(app2);
+ assertBfsl(app3);
+ // 4 is IMP_FG
+ assertBfsl(app5);
}
@SuppressWarnings("GuardedBy")
@@ -2096,7 +2288,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
- final int expected = PROCESS_CAPABILITY_ALL;
+ final int expected = PROCESS_CAPABILITY_ALL & ~PROCESS_CAPABILITY_BFSL;
assertEquals(expected, client.mState.getSetCapability());
assertEquals(expected, client2.mState.getSetCapability());
assertEquals(expected, app.mState.getSetCapability());
@@ -2137,6 +2329,11 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app5, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
+ assertBfsl(app);
+ assertBfsl(app2);
+ assertBfsl(app3);
+ // 4 is IMP_FG
+ assertBfsl(app5);
}
@SuppressWarnings("GuardedBy")
@@ -2444,6 +2641,15 @@
assertEquals(expectedProcState, state.getSetProcState());
assertEquals(expectedAdj, state.getSetAdj());
assertEquals(expectedSchedGroup, state.getSetSchedGroup());
+
+ // Below BFGS should never have BFSL.
+ if (expectedProcState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ assertNoBfsl(app);
+ }
+ // Above FGS should always have BFSL.
+ if (expectedProcState < PROCESS_STATE_FOREGROUND_SERVICE) {
+ assertBfsl(app);
+ }
}
private void assertProcStates(ProcessRecord app, boolean expectedCached,
@@ -2453,5 +2659,14 @@
assertEquals(expectedProcState, state.getSetProcState());
assertEquals(expectedAdj, state.getSetAdj());
assertEquals(expectedAdjType, state.getAdjType());
+
+ // Below BFGS should never have BFSL.
+ if (expectedProcState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ assertNoBfsl(app);
+ }
+ // Above FGS should always have BFSL.
+ if (expectedProcState < PROCESS_STATE_FOREGROUND_SERVICE) {
+ assertBfsl(app);
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
new file mode 100644
index 0000000..327fc19
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.backup.BackupHelper;
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+
+import static org.mockito.Mockito.when;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SystemBackupAgentTest {
+ private static final int NON_SYSTEM_USER_ID = 10;
+
+ private TestableSystemBackupAgent mSystemBackupAgent;
+
+ @Mock private Context mContextMock;
+ @Mock private UserManager mUserManagerMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mSystemBackupAgent = new TestableSystemBackupAgent();
+ when(mContextMock.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
+ }
+
+ @Test
+ public void onCreate_systemUser_addsAllHelpers() {
+ UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM);
+ when(mUserManagerMock.isProfile()).thenReturn(false);
+
+ mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+ assertThat(mSystemBackupAgent.mAddedHelpers)
+ .containsExactly(
+ "account_sync_settings",
+ "preferred_activities",
+ "notifications",
+ "permissions",
+ "usage_stats",
+ "shortcut_manager",
+ "account_manager",
+ "slices",
+ "people",
+ "app_locales",
+ "app_gender");
+ }
+
+ @Test
+ public void onCreate_profileUser_addsProfileEligibleHelpers() {
+ UserHandle userHandle = new UserHandle(NON_SYSTEM_USER_ID);
+ when(mUserManagerMock.isProfile()).thenReturn(true);
+
+ mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+ assertThat(mSystemBackupAgent.mAddedHelpers)
+ .containsExactly(
+ "account_sync_settings",
+ "notifications",
+ "permissions",
+ "app_locales");
+ }
+
+ @Test
+ public void onCreate_nonSystemUser_addsNonSystemEligibleHelpers() {
+ UserHandle userHandle = new UserHandle(NON_SYSTEM_USER_ID);
+ when(mUserManagerMock.isProfile()).thenReturn(false);
+
+ mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+ assertThat(mSystemBackupAgent.mAddedHelpers)
+ .containsExactly(
+ "account_sync_settings",
+ "preferred_activities",
+ "notifications",
+ "permissions",
+ "app_locales",
+ "account_manager",
+ "usage_stats",
+ "shortcut_manager");
+ }
+
+ private class TestableSystemBackupAgent extends SystemBackupAgent {
+ final Set<String> mAddedHelpers = new ArraySet<>();
+
+ @Override
+ public void addHelper(String keyPrefix, BackupHelper helper) {
+ mAddedHelpers.add(keyPrefix);
+ }
+
+ @Override
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+ return mContextMock;
+ }
+
+ @Override
+ public Object getSystemService(@ServiceName @NonNull String name) {
+ return null;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
index 6093f4b..0306655 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
@@ -25,6 +25,7 @@
import android.app.backup.BackupAnnotations.BackupDestination;
import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -35,6 +36,7 @@
import android.content.pm.SigningInfo;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -64,11 +66,17 @@
private static final Signature SIGNATURE_2 = generateSignature((byte) 2);
private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
+ private static final int NON_SYSTEM_USER = 10;
@Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock private PackageManagerInternal mMockPackageManagerInternal;
- @Mock private PackageManager mPackageManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
private BackupEligibilityRules mBackupEligibilityRules;
private int mUserId;
@@ -78,6 +86,7 @@
MockitoAnnotations.initMocks(this);
mUserId = UserHandle.USER_SYSTEM;
+ mockContextForFullUser();
mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
}
@@ -95,6 +104,70 @@
}
@Test
+ public void appIsEligibleForBackup_systemUid_nonSystemUser_notAllowedPackage_returnsFalse()
+ throws Exception {
+ setUpForNonSystemUser();
+
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = mBackupEligibilityRules.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isFalse();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_systemUid_nonSystemUser_allowedPackage_returnsTrue()
+ throws Exception {
+ setUpForNonSystemUser();
+
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = UserBackupManagerService.WALLPAPER_PACKAGE;
+
+ boolean isEligible = mBackupEligibilityRules.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isTrue();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_systemUid_profileUser_notAllowedPackage_returnsFalse()
+ throws Exception {
+ setUpForProfileUser();
+
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = mBackupEligibilityRules.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isFalse();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_systemUid_profileUser_allowedPackage_returnsTrue()
+ throws Exception {
+ setUpForProfileUser();
+
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
+
+ boolean isEligible = mBackupEligibilityRules.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isTrue();
+ }
+
+ @Test
public void appIsEligibleForBackup_systemAppWithoutCustomBackupAgent_returnsFalse()
throws Exception {
ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -790,7 +863,7 @@
private BackupEligibilityRules getBackupEligibilityRules(
@BackupDestination int backupDestination) {
return new BackupEligibilityRules(mPackageManager, mMockPackageManagerInternal, mUserId,
- backupDestination);
+ mContext, backupDestination);
}
private static Signature generateSignature(byte i) {
@@ -813,4 +886,26 @@
return new Property(PackageManager.PROPERTY_ALLOW_ADB_BACKUP, allowAdbBackup,
TEST_PACKAGE_NAME, /* className */ "");
}
+
+ private void setUpForNonSystemUser() {
+ mUserId = NON_SYSTEM_USER;
+ mockContextForFullUser();
+ mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
+ }
+
+ private void setUpForProfileUser() {
+ mUserId = NON_SYSTEM_USER;
+ mockContextForProfile();
+ mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
+ }
+
+ private void mockContextForProfile() {
+ when(mUserManager.isProfile()).thenReturn(true);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+ }
+
+ private void mockContextForFullUser() {
+ when(mUserManager.isProfile()).thenReturn(false);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 30c6975..3399565 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -149,7 +149,7 @@
fileMetadata);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -163,7 +163,7 @@
fileMetadata);
restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -226,7 +226,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, new FileMetadata(), null /* signatures */,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -247,7 +247,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -272,7 +272,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -298,7 +298,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -323,7 +323,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -350,7 +350,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -385,7 +385,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), signatures,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -424,7 +424,7 @@
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), signatures,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -462,7 +462,7 @@
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), signatures,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -504,7 +504,7 @@
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, info, signatures, mMockPackageManagerInternal,
- mUserId);
+ mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -548,7 +548,7 @@
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, signatures, mMockPackageManagerInternal,
- mUserId);
+ mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -588,7 +588,7 @@
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, info, signatures, mMockPackageManagerInternal,
- mUserId);
+ mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 57e873d..6e63315 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -19,13 +19,15 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -41,6 +43,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.util.FloatProperty;
import android.view.Display;
@@ -55,6 +58,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -75,15 +79,20 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerController2Test {
- private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final String UNIQUE_ID = "unique_id_test123";
+ private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+ private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+ private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+ private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
- private DisplayPowerController2.Injector mInjector;
private Context mContextSpy;
+ private DisplayPowerControllerHolder mHolder;
+ private Sensor mProxSensor;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -92,30 +101,14 @@
@Mock
private DisplayBlanker mDisplayBlankerMock;
@Mock
- private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
- @Mock
- private LogicalDisplay mLogicalDisplayMock;
- @Mock
- private DisplayDevice mDisplayDeviceMock;
- @Mock
private BrightnessTracker mBrightnessTrackerMock;
@Mock
- private BrightnessSetting mBrightnessSettingMock;
- @Mock
private WindowManagerPolicy mWindowManagerPolicyMock;
@Mock
private PowerManager mPowerManagerMock;
@Mock
private Resources mResourcesMock;
@Mock
- private DisplayDeviceConfig mDisplayDeviceConfigMock;
- @Mock
- private DisplayPowerState mDisplayPowerStateMock;
- @Mock
- private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
- @Mock
- private WakelockController mWakelockController;
- @Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
@@ -126,6 +119,7 @@
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
.spyStatic(LocalServices.class)
.spyStatic(BatteryStatsService.class)
.startMocking();
@@ -133,52 +127,21 @@
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
mHandler = new Handler(mTestLooper.getLooper());
- mInjector = new DisplayPowerController2.Injector() {
- @Override
- DisplayPowerController2.Clock getClock() {
- return mClock::now;
- }
-
- @Override
- DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
- int displayId, int displayState) {
- return mDisplayPowerStateMock;
- }
-
- @Override
- DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
- FloatProperty<DisplayPowerState> firstProperty,
- FloatProperty<DisplayPowerState> secondProperty) {
- return mDualRampAnimatorMock;
- }
-
- @Override
- WakelockController getWakelockController(int displayId,
- DisplayPowerCallbacks displayPowerCallbacks) {
- return mWakelockController;
- }
-
- @Override
- DisplayPowerProximityStateController getDisplayPowerProximityStateController(
- WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
- Looper looper, Runnable nudgeUpdatePowerState, int displayId,
- SensorManager sensorManager) {
- return new DisplayPowerProximityStateController(wakelockController,
- displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
- sensorManager, /* injector= */ null);
- }
- };
-
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ SystemProperties.set(anyString(), any()));
doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
mCdsiMock).when(() -> LocalServices.getService(
ColorDisplayService.ColorDisplayServiceInternal.class));
- doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
- BatteryStatsService.getService());
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+ mProxSensor = setUpProxSensor();
+
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
}
@After
@@ -189,72 +152,53 @@
@Test
public void testReleaseProxSuspendBlockersOnExit() throws Exception {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController2 dpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
- when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState to start listener for the prox sensor
advanceTime(1);
- SensorEventListener listener = getSensorEventListener(proxSensor);
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
assertNotNull(listener);
- listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, 5 /* lux */));
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mWakelockController).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mWakelockController).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-
- dpc.stop();
+ mHolder.dpc.stop();
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mWakelockController).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mWakelockController).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
}
@Test
- public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
- setUpDisplay(1, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController2 dpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
- when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ final DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState
advanceTime(1);
verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
- eq(proxSensor), anyInt(), any(Handler.class));
+ eq(mProxSensor), anyInt(), any(Handler.class));
}
/**
@@ -284,56 +228,394 @@
return mSensorEventListenerCaptor.getValue();
}
- private void setUpDisplay(int displayId, String uniqueId) {
+ private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+ DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock) {
DisplayInfo info = new DisplayInfo();
DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
- when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
- when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
- when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true);
- when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
- when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
- when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
- when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+ when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+ when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(logicalDisplayMock.isEnabledLocked()).thenReturn(true);
+ when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+ when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+ when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
new DisplayDeviceConfig.SensorData() {
{
type = Sensor.STRING_TYPE_PROXIMITY;
name = null;
}
});
- when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+ when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
}
@Test
- public void testDisplayBrightnessFollowers() {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+ public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- DisplayPowerController2 defaultDpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
- DisplayPowerController2 followerDpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
- defaultDpc.addDisplayBrightnessFollower(followerDpc);
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
- defaultDpc.setBrightness(0.3f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
- defaultDpc.setBrightness(0.6f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ // Test different float scale values
+ float leadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
- float brightness = 0.1f;
- defaultDpc.clearDisplayBrightnessFollowers();
- defaultDpc.setBrightness(brightness);
- assertNotEquals(brightness, followerDpc.getBrightnessInfo().brightness, 0);
+ clearInvocations(mHolder.animator, followerDpc.animator);
+
+ // Test the same float scale value
+ float brightness = 0.6f;
+ nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+
+ @Test
+ public void testDisplayBrightnessFollowersRemoval() {
+ DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+ FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+ SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+ clearInvocations(followerDpc.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+
+ clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+ // Remove the first follower and validate it goes back to its original brightness.
+ mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+ advanceTime(1);
+ verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ clearInvocations(followerDpc.animator);
+
+ // Change the brightness of the lead display and validate only the second follower responds
+ brightness = 0.7f;
+ nits = 700;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+ verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId) {
+ final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+ final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+ final AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ final WakelockController wakelockController = mock(WakelockController.class);
+ final BrightnessMappingStrategy brightnessMappingStrategy =
+ mock(BrightnessMappingStrategy.class);
+ final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+
+ TestInjector injector = new TestInjector(displayPowerState, animator,
+ automaticBrightnessController, wakelockController, brightnessMappingStrategy,
+ hysteresisLevels);
+
+ final LogicalDisplay display = mock(LogicalDisplay.class);
+ final DisplayDevice device = mock(DisplayDevice.class);
+ final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+ final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+ final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+ setUpDisplay(displayId, uniqueId, display, device, config);
+
+ final DisplayPowerController2 dpc = new DisplayPowerController2(
+ mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, display,
+ mBrightnessTrackerMock, brightnessSetting, () -> {},
+ hbmMetadata);
+
+ return new DisplayPowerControllerHolder(dpc, displayPowerState, brightnessSetting, animator,
+ automaticBrightnessController, wakelockController);
+ }
+
+ /**
+ * A class for holding a DisplayPowerController under test and all the mocks specifically
+ * related to it.
+ */
+ private static class DisplayPowerControllerHolder {
+ public final DisplayPowerController2 dpc;
+ public final DisplayPowerState displayPowerState;
+ public final BrightnessSetting brightnessSetting;
+ public final DualRampAnimator<DisplayPowerState> animator;
+ public final AutomaticBrightnessController automaticBrightnessController;
+ public final WakelockController wakelockController;
+
+ DisplayPowerControllerHolder(DisplayPowerController2 dpc,
+ DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+ DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ WakelockController wakelockController) {
+ this.dpc = dpc;
+ this.displayPowerState = displayPowerState;
+ this.brightnessSetting = brightnessSetting;
+ this.animator = animator;
+ this.automaticBrightnessController = automaticBrightnessController;
+ this.wakelockController = wakelockController;
+ }
+ }
+
+ private class TestInjector extends DisplayPowerController2.Injector {
+ private final DisplayPowerState mDisplayPowerState;
+ private final DualRampAnimator<DisplayPowerState> mAnimator;
+ private final AutomaticBrightnessController mAutomaticBrightnessController;
+ private final WakelockController mWakelockController;
+ private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+ private final HysteresisLevels mHysteresisLevels;
+
+ TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ WakelockController wakelockController,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ HysteresisLevels hysteresisLevels) {
+ mDisplayPowerState = dps;
+ mAnimator = animator;
+ mAutomaticBrightnessController = automaticBrightnessController;
+ mWakelockController = wakelockController;
+ mBrightnessMappingStrategy = brightnessMappingStrategy;
+ mHysteresisLevels = hysteresisLevels;
+ }
+
+ @Override
+ DisplayPowerController2.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerState;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mAnimator;
+ }
+
+ @Override
+ WakelockController getWakelockController(int displayId,
+ DisplayPowerCallbacks displayPowerCallbacks) {
+ return mWakelockController;
+ }
+
+ @Override
+ DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+ WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+ SensorManager sensorManager) {
+ return new DisplayPowerProximityStateController(wakelockController,
+ displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+ sensorManager, /* injector= */ null);
+ }
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mAutomaticBrightnessController;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMappingStrategy;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevels;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevels;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 6bf5b62..a8c3e4e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -19,14 +19,15 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -40,7 +41,9 @@
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.util.FloatProperty;
import android.view.Display;
@@ -55,6 +58,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -75,15 +79,20 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerControllerTest {
- private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final String UNIQUE_ID = "unique_id_test123";
+ private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+ private static final String FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_456";
+ private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+ private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
- private DisplayPowerController.Injector mInjector;
private Context mContextSpy;
+ private DisplayPowerControllerHolder mHolder;
+ private Sensor mProxSensor;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -92,28 +101,14 @@
@Mock
private DisplayBlanker mDisplayBlankerMock;
@Mock
- private LogicalDisplay mLogicalDisplayMock;
- @Mock
- private DisplayDevice mDisplayDeviceMock;
- @Mock
- private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
- @Mock
private BrightnessTracker mBrightnessTrackerMock;
@Mock
- private BrightnessSetting mBrightnessSettingMock;
- @Mock
private WindowManagerPolicy mWindowManagerPolicyMock;
@Mock
private PowerManager mPowerManagerMock;
@Mock
private Resources mResourcesMock;
@Mock
- private DisplayDeviceConfig mDisplayDeviceConfigMock;
- @Mock
- private DisplayPowerState mDisplayPowerStateMock;
- @Mock
- private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
- @Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
@@ -124,6 +119,7 @@
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
.spyStatic(LocalServices.class)
.spyStatic(BatteryStatsService.class)
.startMocking();
@@ -131,36 +127,22 @@
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
mHandler = new Handler(mTestLooper.getLooper());
- mInjector = new DisplayPowerController.Injector() {
- @Override
- DisplayPowerController.Clock getClock() {
- return mClock::now;
- }
-
- @Override
- DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
- int displayId, int displayState) {
- return mDisplayPowerStateMock;
- }
-
- @Override
- DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
- FloatProperty<DisplayPowerState> firstProperty,
- FloatProperty<DisplayPowerState> secondProperty) {
- return mDualRampAnimatorMock;
- }
- };
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ SystemProperties.set(anyString(), any()));
doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
mCdsiMock).when(() -> LocalServices.getService(
- ColorDisplayService.ColorDisplayServiceInternal.class));
- doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
- BatteryStatsService.getService());
+ ColorDisplayService.ColorDisplayServiceInternal.class));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+ mProxSensor = setUpProxSensor();
+
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
}
@After
@@ -171,72 +153,55 @@
@Test
public void testReleaseProxSuspendBlockersOnExit() throws Exception {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController dpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
- when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState to start listener for the prox sensor
advanceTime(1);
- SensorEventListener listener = getSensorEventListener(proxSensor);
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
assertNotNull(listener);
- listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, 5 /* lux */));
advanceTime(1);
// two times, one for unfinished business and one for proximity
verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
- dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
- dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
- dpc.stop();
+ mHolder.dpc.stop();
advanceTime(1);
// two times, one for unfinished business and one for proximity
verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
- dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
- dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
}
@Test
- public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
- setUpDisplay(1, UNIQUE_DISPLAY_ID);
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController dpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
- when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState
advanceTime(1);
verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
- eq(proxSensor), anyInt(), any(Handler.class));
+ eq(mProxSensor), anyInt(), any(Handler.class));
}
/**
@@ -266,56 +231,371 @@
return mSensorEventListenerCaptor.getValue();
}
- private void setUpDisplay(int displayId, String uniqueId) {
+ private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+ DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock) {
DisplayInfo info = new DisplayInfo();
DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
- when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
- when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
- when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true);
- when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
- when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
- when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
- when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+ when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+ when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(logicalDisplayMock.isEnabledLocked()).thenReturn(true);
+ when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+ when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+ when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
new DisplayDeviceConfig.SensorData() {
{
type = Sensor.STRING_TYPE_PROXIMITY;
name = null;
}
});
- when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+ when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
}
@Test
- public void testDisplayBrightnessFollowers() {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+ public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
- DisplayPowerController defaultDpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
- DisplayPowerController followerDpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
- defaultDpc.addDisplayBrightnessFollower(followerDpc);
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
- defaultDpc.setBrightness(0.3f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
- defaultDpc.setBrightness(0.6f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ // Test different float scale values
+ float leadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
- float brightness = 0.1f;
- defaultDpc.clearDisplayBrightnessFollowers();
- defaultDpc.setBrightness(brightness);
- assertNotEquals(brightness, followerDpc.getBrightnessInfo().brightness, 0);
+ clearInvocations(mHolder.animator, followerDpc.animator);
+
+ // Test the same float scale value
+ float brightness = 0.6f;
+ nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowersRemoval() {
+ DisplayPowerControllerHolder followerHolder =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ DisplayPowerControllerHolder secondFollowerHolder =
+ createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+ SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+ clearInvocations(followerHolder.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+
+ clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+ // Remove the first follower and validate it goes back to its original brightness.
+ mHolder.dpc.removeDisplayBrightnessFollower(followerHolder.dpc);
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ clearInvocations(followerHolder.animator);
+
+ // Change the brightness of the lead display and validate only the second follower responds
+ brightness = 0.7f;
+ nits = 700;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerHolder.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId) {
+ final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+ final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+ final AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ final BrightnessMappingStrategy brightnessMappingStrategy =
+ mock(BrightnessMappingStrategy.class);
+ final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+
+ DisplayPowerController.Injector injector = new TestInjector(displayPowerState, animator,
+ automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels);
+
+ final LogicalDisplay display = mock(LogicalDisplay.class);
+ final DisplayDevice device = mock(DisplayDevice.class);
+ final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+ final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+ final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+ setUpDisplay(displayId, uniqueId, display, device, config);
+
+ final DisplayPowerController dpc = new DisplayPowerController(
+ mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, display,
+ mBrightnessTrackerMock, brightnessSetting, () -> {},
+ hbmMetadata);
+
+ return new DisplayPowerControllerHolder(dpc, displayPowerState, brightnessSetting, animator,
+ automaticBrightnessController);
+ }
+
+ /**
+ * A class for holding a DisplayPowerController under test and all the mocks specifically
+ * related to it.
+ */
+ private static class DisplayPowerControllerHolder {
+ public final DisplayPowerController dpc;
+ public final DisplayPowerState displayPowerState;
+ public final BrightnessSetting brightnessSetting;
+ public final DualRampAnimator<DisplayPowerState> animator;
+ public final AutomaticBrightnessController automaticBrightnessController;
+
+ DisplayPowerControllerHolder(DisplayPowerController dpc,
+ DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+ DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController) {
+ this.dpc = dpc;
+ this.displayPowerState = displayPowerState;
+ this.brightnessSetting = brightnessSetting;
+ this.animator = animator;
+ this.automaticBrightnessController = automaticBrightnessController;
+ }
+ }
+
+ private class TestInjector extends DisplayPowerController.Injector {
+ private final DisplayPowerState mDisplayPowerState;
+ private final DualRampAnimator<DisplayPowerState> mAnimator;
+ private final AutomaticBrightnessController mAutomaticBrightnessController;
+ private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+ private final HysteresisLevels mHysteresisLevels;
+
+ TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ HysteresisLevels hysteresisLevels) {
+ mDisplayPowerState = dps;
+ mAnimator = animator;
+ mAutomaticBrightnessController = automaticBrightnessController;
+ mBrightnessMappingStrategy = brightnessMappingStrategy;
+ mHysteresisLevels = hysteresisLevels;
+ }
+
+ @Override
+ DisplayPowerController.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerState;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mAnimator;
+ }
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mAutomaticBrightnessController;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMappingStrategy;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevels;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevels;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index d03d196..1ed2f78 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -316,21 +317,10 @@
}
@Test
- public void testGetBootUser_Headless_UserCreatedIfOnlySystemUserExists() throws Exception {
+ public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
setSystemUserHeadless(true);
- int bootUser = mUmi.getBootUser();
-
- assertWithMessage("getStartingUser")
- .that(bootUser).isNotEqualTo(UserHandle.USER_SYSTEM);
-
- UserData newUser = mUsers.get(bootUser);
- assertWithMessage("New boot user is a full user")
- .that(newUser.info.isFull()).isTrue();
- assertWithMessage("New boot user is an admin user")
- .that(newUser.info.isAdmin()).isTrue();
- assertWithMessage("New boot user is the main user")
- .that(newUser.info.isMain()).isTrue();
+ assertThrows(UserManager.CheckedUserOperationException.class, () -> mUmi.getBootUser());
}
private void mockCurrentUser(@UserIdInt int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 9b48114..f8955ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -200,7 +200,6 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
ExtendedMockito.doAnswer(invocation -> {
int userId = (invocation.getArgument(0));
return getWallpaperTestDir(userId);
@@ -398,7 +397,8 @@
TypedXmlSerializer serializer = Xml.newBinarySerializer();
serializer.setOutput(new ByteArrayOutputStream(), StandardCharsets.UTF_8.name());
serializer.startDocument(StandardCharsets.UTF_8.name(), true);
- mService.writeWallpaperAttributes(serializer, "wp", systemWallpaperData);
+ mService.mWallpaperDataParser.writeWallpaperAttributes(
+ serializer, "wp", systemWallpaperData);
} catch (IOException e) {
fail("exception occurred while writing system wallpaper attributes");
}
@@ -409,7 +409,7 @@
systemWallpaperData.cropFile.getAbsolutePath());
try {
TypedXmlPullParser parser = Xml.newBinaryPullParser();
- mService.parseWallpaperAttributes(parser, shouldMatchSystem, true);
+ mService.mWallpaperDataParser.parseWallpaperAttributes(parser, shouldMatchSystem, true);
} catch (XmlPullParserException e) {
fail("exception occurred while parsing wallpaper");
}
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 245db46..ae78dfe 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -40,7 +40,6 @@
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -166,36 +165,6 @@
}
@Test
- public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException {
- prepApexInfo();
- List result = mTestInterface.getApexInfo();
- Assert.assertNotNull("Apex info map should not be null", result);
- // TODO(265244016): When PackageManagerInternal is a mock, it's harder to keep the
- // `measurePackage` working in unit test. Disable it for now. We may need more refactoring
- // or cover this in integration tests.
- // Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
- }
-
- @Test
- public void getApexInfo_postInitialize_returnsActualApexs()
- throws RemoteException, PackageManager.NameNotFoundException {
- prepApexInfo();
- List resultList = mTestInterface.getApexInfo();
-
- PackageManager pm = mContext.getPackageManager();
- Assert.assertNotNull(pm);
- List<Bundle> castedResult = (List<Bundle>) resultList;
- for (Bundle resultBundle : castedResult) {
- String packageName = resultBundle.getString(
- BinaryTransparencyService.BUNDLE_PACKAGE_NAME);
- Assert.assertNotNull("Package name for APEX should not be null", packageName);
- Assert.assertTrue(packageName + "is not an APEX!",
- resultBundle.getBoolean(
- BinaryTransparencyService.BUNDLE_PACKAGE_IS_APEX));
- }
- }
-
- @Test
public void testCollectBiometricProperties_disablesFeature() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index d996e37..bf23d9d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -19,6 +19,7 @@
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
+import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -48,6 +49,9 @@
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.accessibility.MagnificationAnimationCallback;
@@ -55,6 +59,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
@@ -71,6 +76,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
+import org.testng.Assert;
import java.util.Locale;
@@ -93,6 +99,7 @@
static final int DISPLAY_1 = 1;
static final int DISPLAY_COUNT = 2;
static final int INVALID_DISPLAY = 2;
+ private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
@@ -105,8 +112,8 @@
MagnificationInfoChangedCallback.class);
private final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(
null);
- private final MagnificationScaleProvider mScaleProvider = mock(
- MagnificationScaleProvider.class);
+ private MagnificationScaleProvider mScaleProvider;
+ private MockContentResolver mResolver;
private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass(
MagnificationConfig.class);
@@ -129,6 +136,12 @@
when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+ mResolver = new MockContentResolver();
+ mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mMockContext.getContentResolver()).thenReturn(mResolver);
+ Settings.Secure.putFloatForUser(mResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ CURRENT_USER_ID);
initMockWindowManager();
final DisplayInfo displayInfo = new DisplayInfo();
@@ -137,6 +150,7 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+ mScaleProvider = new MagnificationScaleProvider(mMockContext);
mFullScreenMagnificationController = new FullScreenMagnificationController(
mMockControllerCtx, new Object(), mRequestObserver, mScaleProvider);
}
@@ -1168,6 +1182,20 @@
verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
}
+ @Test
+ public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY);
+
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, 1.0f, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
+ persistedScale);
+ }
+
private void setScaleToMagnifying() {
register(DISPLAY_0);
float scale = 2.0f;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 25ad2be..d841dfc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -274,6 +274,19 @@
}
@Test
+ public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ final float persistedScale = mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY);
+
+ mWindowMagnificationManager.setScale(TEST_DISPLAY, 1.0f);
+ mWindowMagnificationManager.persistScale(TEST_DISPLAY);
+
+ assertEquals(Settings.Secure.getFloatForUser(mResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f,
+ CURRENT_USER_ID), persistedScale);
+ }
+
+ @Test
public void scaleSetterGetter_enabledOnTestDisplay_expectedValue() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index b146c27..18d629f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -412,7 +412,7 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
+ verify(mInjector, times(0)).dismissKeyguard(any());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
verifySystemUserVisibilityChangesNeverNotified();
@@ -433,7 +433,7 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
+ verify(mInjector, times(1)).dismissKeyguard(any());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
verifySystemUserVisibilityChangesNeverNotified();
@@ -1148,7 +1148,7 @@
}
@Override
- protected void dismissKeyguard(Runnable runnable, String reason) {
+ protected void dismissKeyguard(Runnable runnable) {
runnable.run();
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 4358e9e..8f2a1e5 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
+import android.view.Display;
import android.view.DisplayAddress;
import androidx.test.filters.SmallTest;
@@ -65,11 +66,13 @@
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
/* isEnabled= */ false, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
assertEquals(testLayout, configLayout);
}
@@ -81,11 +84,13 @@
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
/* isEnabled= */ false, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
assertEquals(testLayout, configLayout);
}
@@ -99,13 +104,15 @@
Layout.Display display1 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
display1.setPosition(Layout.Display.POSITION_FRONT);
Layout.Display display2 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
display2.setPosition(Layout.Display.POSITION_REAR);
assertEquals(testLayout, configLayout);
@@ -127,12 +134,14 @@
Layout.Display display1 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
display1.setRefreshRateZoneId("test1");
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
assertEquals(testLayout, configLayout);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 8a37ed9..ac1dca9 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -299,9 +299,11 @@
Layout layout1 = new Layout();
layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
assertThat(layout1.size()).isEqualTo(2);
final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -335,16 +337,19 @@
Layout layout1 = new Layout();
layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
final int layoutState2 = 2;
Layout layout2 = new Layout();
layout2.createDisplayLocked(info(device2).address, /* isDefault= */ false,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
// Device3 is the default display.
layout2.createDisplayLocked(info(device3).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
assertThat(layout2.size()).isEqualTo(2);
final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -567,17 +572,21 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
true, true, mIdProducer,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
false, true, mIdProducer,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- false, false, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ false, false, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
@@ -604,6 +613,10 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getLeadDisplayIdLocked());
+ assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getLeadDisplayIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
.getBrightnessThrottlingDataIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
@@ -655,19 +668,22 @@
/* isDefault= */ true,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
@@ -703,19 +719,22 @@
/* isDefault= */ true,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ false,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ false,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
@@ -790,10 +809,11 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
false, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
- POSITION_REAR);
+ POSITION_REAR, Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 5ca695b..c9612cd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -49,8 +49,8 @@
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
-import android.provider.Settings;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
@@ -83,16 +83,15 @@
protected static final int MANAGED_PROFILE_USER_ID = 12;
protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
protected static final int SECONDARY_USER_ID = 20;
+ protected static final int TERTIARY_USER_ID = 21;
- private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
- | UserInfo.FLAG_MAIN);
- private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED);
+ protected UserInfo mPrimaryUserInfo;
+ protected UserInfo mSecondaryUserInfo;
+ protected UserInfo mTertiaryUserInfo;
private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
- LockSettingsService mService;
+ LockSettingsServiceTestable mService;
LockSettingsInternal mLocalService;
MockLockSettingsContext mContext;
@@ -117,6 +116,7 @@
FingerprintManager mFingerprintManager;
FaceManager mFaceManager;
PackageManager mPackageManager;
+ LockSettingsServiceTestable.MockInjector mInjector;
@Rule
public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@@ -162,22 +162,61 @@
mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
mUserManager, mPasswordSlotManager);
mAuthSecretService = mock(IAuthSecret.class);
- mService = new LockSettingsServiceTestable(mContext, mStorage,
- mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
- mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
- mUserManagerInternal, mDeviceStateCache);
+ mInjector =
+ new LockSettingsServiceTestable.MockInjector(
+ mContext,
+ mStorage,
+ mKeyStore,
+ mActivityManager,
+ setUpStorageManagerMock(),
+ mSpManager,
+ mGsiService,
+ mRecoverableKeyStoreManager,
+ mUserManagerInternal,
+ mDeviceStateCache);
+ mService =
+ new LockSettingsServiceTestable(mInjector, mGateKeeperService, mAuthSecretService);
mService.mHasSecureLockScreen = true;
- when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
- mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
+ mPrimaryUserInfo =
+ new UserInfo(
+ PRIMARY_USER_ID,
+ null,
+ null,
+ UserInfo.FLAG_INITIALIZED
+ | UserInfo.FLAG_ADMIN
+ | UserInfo.FLAG_PRIMARY
+ | UserInfo.FLAG_MAIN
+ | UserInfo.FLAG_FULL);
+ mSecondaryUserInfo =
+ new UserInfo(
+ SECONDARY_USER_ID,
+ null,
+ null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
+ mTertiaryUserInfo =
+ new UserInfo(
+ TERTIARY_USER_ID,
+ null,
+ null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
+
+ when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
+ when(mUserManagerInternal.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
+ mPrimaryUserProfiles.add(mPrimaryUserInfo);
installChildProfile(MANAGED_PROFILE_USER_ID);
installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
for (UserInfo profile : mPrimaryUserProfiles) {
when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles);
}
- when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+ when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(mSecondaryUserInfo);
+ when(mUserManagerInternal.getUserInfo(eq(SECONDARY_USER_ID)))
+ .thenReturn(mSecondaryUserInfo);
+ when(mUserManager.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
+ when(mUserManagerInternal.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
- allUsers.add(SECONDARY_USER_INFO);
+ allUsers.add(mSecondaryUserInfo);
+ allUsers.add(mTertiaryUserInfo);
when(mUserManager.getUsers()).thenReturn(allUsers);
when(mActivityManager.unlockUser2(anyInt(), any())).thenAnswer(
@@ -227,9 +266,10 @@
userInfo.profileGroupId = PRIMARY_USER_ID;
mPrimaryUserProfiles.add(userInfo);
when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
- when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
+ when(mUserManager.getProfileParent(eq(profileId))).thenReturn(mPrimaryUserInfo);
when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+ when(mUserManagerInternal.getUserInfo(eq(profileId))).thenReturn(userInfo);
// TODO(b/258213147): Remove
when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId)))
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index f0f0632..9686c38 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -30,6 +30,7 @@
import android.os.storage.IStorageManager;
import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.service.gatekeeper.IGateKeeperService;
import com.android.internal.widget.LockscreenCredential;
import com.android.server.ServiceThread;
@@ -40,7 +41,7 @@
public class LockSettingsServiceTestable extends LockSettingsService {
- private static class MockInjector extends LockSettingsService.Injector {
+ public static class MockInjector extends LockSettingsService.Injector {
private LockSettingsStorage mLockSettingsStorage;
private KeyStore mKeyStore;
@@ -52,6 +53,9 @@
private UserManagerInternal mUserManagerInternal;
private DeviceStateCache mDeviceStateCache;
+ public boolean mIsHeadlessSystemUserMode = false;
+ public boolean mIsMainUserPermanentAdmin = false;
+
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager,
IStorageManager storageManager, SyntheticPasswordManager spManager,
@@ -140,19 +144,22 @@
return mock(ManagedProfilePasswordCache.class);
}
+ @Override
+ public boolean isHeadlessSystemUserMode() {
+ return mIsHeadlessSystemUserMode;
+ }
+
+ @Override
+ public boolean isMainUserPermanentAdmin() {
+ return mIsMainUserPermanentAdmin;
+ }
}
- public MockInjector mInjector;
-
- protected LockSettingsServiceTestable(Context context,
- LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
- IStorageManager storageManager, IActivityManager mActivityManager,
- SyntheticPasswordManager spManager, IAuthSecret authSecretService,
- FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
- super(new MockInjector(context, storage, keystore, mActivityManager,
- storageManager, spManager, gsiService, recoverableKeyStoreManager,
- userManagerInternal, deviceStateCache));
+ protected LockSettingsServiceTestable(
+ LockSettingsService.Injector injector,
+ IGateKeeperService gatekeeper,
+ IAuthSecret authSecretService) {
+ super(injector);
mGateKeeperService = gatekeeper;
mAuthSecretService = authSecretService;
}
@@ -199,4 +206,10 @@
UserInfo userInfo = mUserManager.getUserInfo(userId);
return userInfo.isCloneProfile() || userInfo.isManagedProfile();
}
+
+ void clearAuthSecret() {
+ synchronized (mHeadlessAuthSecretLock) {
+ mAuthSecret = null;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 57593cf..62d8a76 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,6 +16,10 @@
package com.android.server.locksettings;
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_MAIN;
+import static android.content.pm.UserInfo.FLAG_PRIMARY;
+
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
@@ -27,9 +31,9 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -247,6 +251,15 @@
@Test
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
+ initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
+ reset(mAuthSecretService);
+ mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
+ }
+
+ @Test
+ public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecretIfPasswordIsCleared()
+ throws RemoteException {
LockscreenCredential password = newPassword("password");
initSpAndSetCredential(PRIMARY_USER_ID, password);
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
@@ -256,6 +269,56 @@
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
+ private void setupHeadlessTest() {
+ mInjector.mIsHeadlessSystemUserMode = true;
+ mInjector.mIsMainUserPermanentAdmin = true;
+ mPrimaryUserInfo.flags &= ~(FLAG_FULL | FLAG_PRIMARY);
+ mSecondaryUserInfo.flags |= FLAG_MAIN;
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(SECONDARY_USER_ID);
+ mService.initializeSyntheticPassword(TERTIARY_USER_ID);
+ reset(mAuthSecretService);
+ }
+
+ @Test
+ public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
+ }
+
+ @Test
+ public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
+ }
+
+ @Test
+ public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ var captor = ArgumentCaptor.forClass(byte[].class);
+ verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
+ var value = captor.getValue();
+ reset(mAuthSecretService);
+ mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
+ }
+
+ @Test
+ public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ var captor = ArgumentCaptor.forClass(byte[].class);
+ verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
+ var value = captor.getValue();
+ mService.clearAuthSecret();
+ reset(mAuthSecretService);
+ mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
+ }
+
@Test
public void testTokenBasedResetPassword() throws RemoteException {
LockscreenCredential password = newPassword("password");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 966c047..50f3a88 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -8,6 +8,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
import com.google.android.collect.Sets;
import org.junit.Before;
@@ -29,7 +30,7 @@
// sequentially, starting at slot 0.
@Test
public void testFrpWeaverSlotNotReused() {
- final int userId = 10;
+ final int userId = SECONDARY_USER_ID;
final int frpWeaverSlot = 0;
setDeviceProvisioned(false);
@@ -45,7 +46,7 @@
// it's here as a control for testFrpWeaverSlotNotReused().
@Test
public void testFrpWeaverSlotReused() {
- final int userId = 10;
+ final int userId = SECONDARY_USER_ID;
final int frpWeaverSlot = 0;
setDeviceProvisioned(true);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index bb79fd8..b9adc30 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -40,6 +40,7 @@
import android.Manifest;
import android.app.KeyguardManager;
import android.app.PendingIntent;
+import android.app.StartLockscreenValidationRequest;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
@@ -61,11 +62,13 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.security.SecureBox;
+import com.android.server.locksettings.LockSettingsService;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -160,14 +163,17 @@
@Mock private ApplicationKeyStorage mApplicationKeyStorage;
@Mock private CleanupManager mCleanupManager;
@Mock private ScheduledExecutorService mExecutorService;
+ @Mock private LockSettingsService mLockSettingsService;
@Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
+ private int mUserId;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
private File mDatabaseFile;
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private RecoverySessionStorage mRecoverySessionStorage;
private RecoverySnapshotStorage mRecoverySnapshotStorage;
private PlatformEncryptionKey mPlatformEncryptionKey;
+ private RemoteLockscreenValidationSessionStorage mRemoteLockscreenValidationSessionStorage;
@Before
public void setUp() throws Exception {
@@ -177,7 +183,9 @@
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+ mUserId = UserHandle.getCallingUserId();
mRecoverySessionStorage = new RecoverySessionStorage();
+ mRemoteLockscreenValidationSessionStorage = new RemoteLockscreenValidationSessionStorage();
when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
when(mMockContext.getSystemServiceName(any())).thenReturn("test");
@@ -198,7 +206,8 @@
mPlatformKeyManager,
mApplicationKeyStorage,
mTestOnlyInsecureCertificateHelper,
- mCleanupManager);
+ mCleanupManager,
+ mRemoteLockscreenValidationSessionStorage);
}
@After
@@ -1269,6 +1278,85 @@
verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
}
+ @Test
+ public void startRemoteLockscreenValidation_credentialsNotSet() throws Exception {
+ when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
+ LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ try {
+ mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
+ fail("should have thrown");
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage()).contains("not set");
+ }
+ verify(mLockSettingsService).getCredentialType(mUserId);
+ mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
+ }
+
+ @Test
+ public void startRemoteLockscreenValidation_checksPermission() throws Exception {
+ when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
+ LockPatternUtils.CREDENTIAL_TYPE_PIN);
+ mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
+ // TODO(b/254335492): Check new system permission
+ verify(mMockContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.RECOVER_KEYSTORE), any());
+ mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
+ }
+
+ @Test
+ public void startRemoteLockscreenValidation_returnsCredentailsType() throws Exception {
+ when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
+ LockPatternUtils.CREDENTIAL_TYPE_PIN);
+ StartLockscreenValidationRequest request =
+ mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
+ int credetialsType = request.getLockscreenUiType();
+
+ assertThat(credetialsType).isEqualTo(KeyguardManager.PIN);
+
+ verify(mLockSettingsService).getCredentialType(anyInt());
+ mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
+ }
+
+ @Test
+ public void startRemoteLockscreenValidation_returnsRemainingAttempts() throws Exception {
+ when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3);
+ StartLockscreenValidationRequest request =
+ mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
+ int credetialsType = request.getLockscreenUiType();
+ assertThat(credetialsType).isEqualTo(KeyguardManager.PATTERN);
+ // TODO(b/254335492): Verify returned value
+ mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
+ }
+
+ @Test
+ public void startRemoteLockscreenValidation_password() throws Exception {
+ when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3);
+ StartLockscreenValidationRequest request =
+ mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
+ int credetialsType = request.getLockscreenUiType();
+ assertThat(credetialsType).isEqualTo(KeyguardManager.PASSWORD);
+ mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
+ }
+
+ @Test
+ public void validateRemoteLockscreen_noActiveSession() throws Exception {
+ when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
+ LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ byte[] invalidGuess = new byte[]{1, 2, 3};
+ try {
+ mRecoverableKeyStoreManager.validateRemoteLockscreen(invalidGuess,
+ mLockSettingsService);
+ fail("should have thrown");
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage()).contains("session");
+ }
+ }
+
private static byte[] encryptedApplicationKey(
SecretKey recoveryKey, byte[] applicationKey) throws Exception {
return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c760efd..b4dd631 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -72,6 +72,7 @@
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // 30 years
private static final int SWITCH_USER_TIMEOUT_SECONDS = 40; // 40 seconds
+ private static final int REMOVE_USER_TIMEOUT_SECONDS = 40; // 40 seconds
// Packages which are used during tests.
private static final String[] PACKAGES = new String[] {
@@ -302,7 +303,7 @@
@MediumTest
@Test
public void testRemoveUserByHandle_ThrowsException() {
- assertThrows(IllegalArgumentException.class, () -> removeUser(null));
+ assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null));
}
@MediumTest
@@ -1464,6 +1465,12 @@
}
private void runThenWaitForUserRemoval(Runnable runnable, int userIdToWaitUntilDeleted) {
+ if (!hasUser(userIdToWaitUntilDeleted)) {
+ runnable.run();
+ mUsersToRemove.remove(userIdToWaitUntilDeleted);
+ return;
+ }
+
Function<Intent, Boolean> checker = intent -> {
UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
return userHandle != null && userHandle.getIdentifier() == userIdToWaitUntilDeleted;
@@ -1472,6 +1479,7 @@
BlockingBroadcastReceiver blockingBroadcastReceiver = BlockingBroadcastReceiver.create(
mContext, Intent.ACTION_USER_REMOVED, checker);
+ blockingBroadcastReceiver.setTimeout(REMOVE_USER_TIMEOUT_SECONDS);
blockingBroadcastReceiver.register();
try (blockingBroadcastReceiver) {
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 abc0c14..1ce8c61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -105,6 +105,7 @@
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
@@ -123,6 +124,7 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.view.ContentRecordingSession;
+import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -1831,6 +1833,111 @@
}
@Test
+ public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
+ // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
+ doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+
+ final DisplayRotationCoordinator coordinator =
+ mRootWindowContainer.getDisplayRotationCoordinator();
+ final DisplayContent defaultDisplayContent = mDisplayContent;
+ final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
+ coordinator.removeDefaultDisplayRotationChangedCallback();
+
+ DeviceStateController deviceStateController = mock(DeviceStateController.class);
+ when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
+ .thenReturn(true);
+
+ // Create secondary display
+ final DisplayContent secondaryDisplayContent =
+ createSecondaryDisplayContent(Display.TYPE_INTERNAL, deviceStateController);
+ final DisplayRotation secondaryDisplayRotation =
+ secondaryDisplayContent.getDisplayRotation();
+ try {
+ // TestDisplayContent bypasses this method but we need it for this test
+ doCallRealMethod().when(secondaryDisplayRotation).updateRotationUnchecked(anyBoolean());
+
+ // TestDisplayContent creates this as a mock. Lets set it up to test our use case.
+ when(secondaryDisplayContent.mDeviceStateController
+ .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()).thenReturn(
+ true);
+
+ // Check that secondary display registered callback
+ assertEquals(secondaryDisplayRotation.mDefaultDisplayRotationChangedCallback,
+ coordinator.mDefaultDisplayRotationChangedCallback);
+
+ // Set the default display to a known orientation. This may be a zero or non-zero
+ // rotation since mDisplayInfo.logicalWidth/Height depends on the DUT's default display
+ defaultDisplayRotation.updateOrientation(SCREEN_ORIENTATION_PORTRAIT, false);
+ assertEquals(defaultDisplayRotation.mPortraitRotation,
+ defaultDisplayRotation.getRotation());
+ assertEquals(defaultDisplayRotation.mPortraitRotation,
+ coordinator.getDefaultDisplayCurrentRotation());
+
+ // Check that in the initial state, the secondary display is in the right rotation
+ assertRotationsAreCorrectlyReversed(defaultDisplayRotation.getRotation(),
+ secondaryDisplayRotation.getRotation());
+
+ // Update primary display rotation, check display coordinator rotation is the default
+ // display's landscape rotation, and that the secondary display rotation is correct.
+ defaultDisplayRotation.updateOrientation(SCREEN_ORIENTATION_LANDSCAPE, false);
+ assertEquals(defaultDisplayRotation.mLandscapeRotation,
+ defaultDisplayRotation.getRotation());
+ assertEquals(defaultDisplayRotation.mLandscapeRotation,
+ coordinator.getDefaultDisplayCurrentRotation());
+ assertRotationsAreCorrectlyReversed(defaultDisplayRotation.getRotation(),
+ secondaryDisplayRotation.getRotation());
+ } finally {
+ secondaryDisplayRotation.removeDefaultDisplayRotationChangedCallback();
+ }
+ }
+
+ @Test
+ public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
+ // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
+ doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+
+ final DisplayRotationCoordinator coordinator =
+ mRootWindowContainer.getDisplayRotationCoordinator();
+ coordinator.removeDefaultDisplayRotationChangedCallback();
+
+ DeviceStateController deviceStateController = mock(DeviceStateController.class);
+ when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
+ .thenReturn(true);
+
+ // Create secondary non-internal displays
+ createSecondaryDisplayContent(Display.TYPE_EXTERNAL, deviceStateController);
+ assertNull(coordinator.mDefaultDisplayRotationChangedCallback);
+ createSecondaryDisplayContent(Display.TYPE_VIRTUAL, deviceStateController);
+ assertNull(coordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ private DisplayContent createSecondaryDisplayContent(int displayType,
+ @NonNull DeviceStateController deviceStateController) {
+ final DisplayInfo secondaryDisplayInfo = new DisplayInfo();
+ secondaryDisplayInfo.copyFrom(mDisplayInfo);
+ secondaryDisplayInfo.type = displayType;
+
+ return new TestDisplayContent.Builder(mAtm, secondaryDisplayInfo)
+ .setDeviceStateController(deviceStateController)
+ .build();
+ }
+
+ private static void assertRotationsAreCorrectlyReversed(@Surface.Rotation int rotation1,
+ @Surface.Rotation int rotation2) {
+ if (rotation1 == ROTATION_0) {
+ assertEquals(rotation1, rotation2);
+ } else if (rotation1 == ROTATION_180) {
+ assertEquals(rotation1, rotation2);
+ } else if (rotation1 == ROTATION_90) {
+ assertEquals(ROTATION_270, rotation2);
+ } else if (rotation1 == ROTATION_270) {
+ assertEquals(ROTATION_90, rotation2);
+ } else {
+ throw new IllegalArgumentException("Unknown rotation: " + rotation1 + ", " + rotation2);
+ }
+ }
+
+ @Test
public void testRemoteRotation() {
final DisplayContent dc = mDisplayContent;
final DisplayRotation dr = dc.getDisplayRotation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
new file mode 100644
index 0000000..4557df0
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.platform.test.annotations.Presubmit;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test class for {@link DisplayRotationCoordinator}
+ *
+ * Build/Install/Run:
+ * atest DisplayRotationCoordinatorTests
+ */
+@SmallTest
+@Presubmit
+public class DisplayRotationCoordinatorTests {
+
+ @NonNull
+ private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
+
+ @Test
+ public void testDefaultDisplayRotationChangedWhenNoCallbackRegistered() {
+ // Does not cause NPE
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ }
+
+ @Test (expected = UnsupportedOperationException.class)
+ public void testSecondRegistrationWithoutRemovingFirst() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
+ public void testSecondRegistrationAfterRemovingFirst() {
+ Runnable callback1 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback();
+
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ verify(callback2).run();
+ verify(callback1, never()).run();
+ }
+
+ @Test
+ public void testRegisterThenDefaultDisplayRotationChanged() {
+ Runnable callback = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
+ verify(callback, never()).run();
+
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ verify(callback).run();
+ assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
+ }
+
+ @Test
+ public void testDefaultDisplayRotationChangedThenRegister() {
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ Runnable callback = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ verify(callback).run();
+ assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index ed2b0a3..21e8ec4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -1135,7 +1135,7 @@
mDeviceStateController = mock(DeviceStateController.class);
mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object(),
- mDeviceStateController) {
+ mDeviceStateController, mock(DisplayRotationCoordinator.class)) {
@Override
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
WindowManagerService service, DisplayContent displayContent) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index 4eaae9f..d1a41ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -552,7 +552,7 @@
/** Please use the {@link Builder} to create. */
DualDisplayContent(RootWindowContainer rootWindowContainer,
Display display) {
- super(rootWindowContainer, display);
+ super(rootWindowContainer, display, mock(DeviceStateController.class));
mFirstRoot = getGroupRoot(FEATURE_FIRST_ROOT);
mSecondRoot = getGroupRoot(FEATURE_SECOND_ROOT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index bcaf886..0037e57 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -24,7 +24,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManager;
@@ -289,6 +291,14 @@
assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+
+ // If there will be display size change when switching from preferred mode to default mode,
+ // then keep the current preferred mode during animating.
+ mDisplayInfo = spy(mDisplayInfo);
+ final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
+ doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
+ mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
+ assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 42bbd2d..94193f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -352,6 +352,7 @@
mAtmService.setWindowManager(mWmService);
mWmService.mDisplayEnabled = true;
mWmService.mDisplayReady = true;
+ mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
// Set configuration for default display
mWmService.getDefaultDisplayContentLocked().reconfigureDisplayLocked();
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 5099869..dab842c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -45,11 +45,13 @@
import static org.mockito.Mockito.clearInvocations;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
@@ -299,35 +301,44 @@
@Test
public void testEmbeddedTaskFragmentEnterPip_singleActivity_resetOrganizerOverrideConfig() {
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setOrganizer(mOrganizer)
- .setFragmentToken(new Binder())
- .setCreateParentTask()
- .createActivityCount(1)
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment taskFragment0 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
+ final TaskFragment taskFragment1 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
.build();
- final Task task = taskFragment.getTask();
- final ActivityRecord activity = taskFragment.getTopMostActivity();
+ final ActivityRecord activity = taskFragment0.getTopMostActivity();
final Rect taskFragmentBounds = new Rect(0, 0, 300, 1000);
task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- taskFragment.setBounds(taskFragmentBounds);
+ taskFragment0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ taskFragment0.setBounds(taskFragmentBounds);
+ taskFragment0.setAdjacentTaskFragment(taskFragment1);
+ taskFragment0.setCompanionTaskFragment(taskFragment1);
+ taskFragment0.setAnimationParams(new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build());
assertEquals(taskFragmentBounds, activity.getBounds());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
+ assertEquals(taskFragment1, taskFragment0.getAdjacentTaskFragment());
+ assertEquals(taskFragment1, taskFragment0.getCompanionTaskFragment());
+ assertNotEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
// Move activity to pinned root task.
mRootWindowContainer.moveActivityToPinnedRootTask(activity,
null /* launchIntoPipHostActivity */, "test");
// Ensure taskFragment requested config is reset.
- assertEquals(taskFragment, activity.getOrganizedTaskFragment());
+ assertEquals(taskFragment0, activity.getOrganizedTaskFragment());
assertEquals(task, activity.getTask());
assertTrue(task.inPinnedWindowingMode());
- assertTrue(taskFragment.inPinnedWindowingMode());
+ assertTrue(taskFragment0.inPinnedWindowingMode());
final Rect taskBounds = task.getBounds();
- assertEquals(taskBounds, taskFragment.getBounds());
+ assertEquals(taskBounds, taskFragment0.getBounds());
assertEquals(taskBounds, activity.getBounds());
- assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, taskFragment0.getRequestedOverrideConfiguration());
+ assertNull(taskFragment0.getAdjacentTaskFragment());
+ assertNull(taskFragment0.getCompanionTaskFragment());
+ assertEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
// Because the whole Task is entering PiP, no need to record for future reparent.
assertNull(activity.mLastTaskFragmentOrganizerBeforePip);
}
@@ -335,18 +346,8 @@
@Test
public void testEmbeddedTaskFragmentEnterPip_multiActivities_notifyOrganizer() {
final Task task = createTask(mDisplayContent);
- final TaskFragment taskFragment0 = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(mOrganizer)
- .setFragmentToken(new Binder())
- .createActivityCount(1)
- .build();
- final TaskFragment taskFragment1 = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(mOrganizer)
- .setFragmentToken(new Binder())
- .createActivityCount(1)
- .build();
+ final TaskFragment taskFragment0 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
+ final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 83be4f0..fec079b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -27,8 +27,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -51,8 +53,9 @@
public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
/** Please use the {@link Builder} to create, visible for use in test builder overrides only. */
- TestDisplayContent(RootWindowContainer rootWindowContainer, Display display) {
- super(display, rootWindowContainer);
+ TestDisplayContent(RootWindowContainer rootWindowContainer, Display display,
+ @NonNull DeviceStateController deviceStateController) {
+ super(display, rootWindowContainer, deviceStateController);
// Normally this comes from display-properties as exposed by WM. Without that, just
// hard-code to FULLSCREEN for tests.
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -97,6 +100,8 @@
private int mStatusBarHeight = 0;
private SettingsEntry mOverrideSettings;
private DisplayMetrics mDisplayMetrics;
+ @NonNull
+ private DeviceStateController mDeviceStateController = mock(DeviceStateController.class);
@Mock
Context mMockContext;
@Mock
@@ -198,8 +203,13 @@
com.android.internal.R.dimen.default_minimal_size_resizable_task);
return this;
}
+ Builder setDeviceStateController(@NonNull DeviceStateController deviceStateController) {
+ mDeviceStateController = deviceStateController;
+ return this;
+ }
TestDisplayContent createInternal(Display display) {
- return new TestDisplayContent(mService.mRootWindowContainer, display);
+ return new TestDisplayContent(mService.mRootWindowContainer, display,
+ mDeviceStateController);
}
TestDisplayContent build() {
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index e5efe05..3e8d259 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -79,6 +79,7 @@
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -750,11 +751,11 @@
}
/**
- * Creates a {@link TaskFragment} with {@link ActivityRecord} and attach it to the
+ * Creates a {@link TaskFragment} with {@link ActivityRecord}, and attaches it to the
* {@code parentTask}.
*
- * @param parentTask the {@link Task} this TaskFragment is going to be attached
- * @return the created TaskFragment
+ * @param parentTask the {@link Task} this {@link TaskFragment} is going to be attached.
+ * @return the created {@link TaskFragment}
*/
static TaskFragment createTaskFragmentWithActivity(@NonNull Task parentTask) {
return new TaskFragmentBuilder(parentTask.mAtmService)
@@ -763,13 +764,27 @@
.build();
}
+ /**
+ * Creates an embedded {@link TaskFragment} organized by {@code organizer} with
+ * {@link ActivityRecord}, and attaches it to the {@code parentTask}.
+ *
+ * @param parentTask the {@link Task} this {@link TaskFragment} is going to be attached.
+ * @param organizer the {@link TaskFragmentOrganizer} this {@link TaskFragment} is going to be
+ * organized by.
+ * @return the created {@link TaskFragment}
+ */
static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask,
- TaskFragmentOrganizer organizer) {
- return new TaskFragmentBuilder(parentTask.mAtmService)
+ @NonNull TaskFragmentOrganizer organizer) {
+ final IBinder fragmentToken = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(parentTask.mAtmService)
.setParentTask(parentTask)
.createActivityCount(1)
.setOrganizer(organizer)
+ .setFragmentToken(fragmentToken)
.build();
+ parentTask.mAtmService.mWindowOrganizerController.mLaunchTaskFragments
+ .put(fragmentToken, taskFragment);
+ return taskFragment;
}
/** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
index affa6b6..7f11128 100644
--- a/telecomm/java/android/telecom/CallStreamingService.java
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -79,7 +79,7 @@
break;
case MSG_CALL_STREAMING_CHANGED_CHANGED:
int state = (int) msg.obj;
- mCall.setStreamingState(state);
+ mCall.requestStreamingState(state);
CallStreamingService.this.onCallStreamingStateChanged(state);
break;
default:
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
index 985cccc..4b27dd6 100644
--- a/telecomm/java/android/telecom/StreamingCall.java
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -172,7 +172,7 @@
* to request holding, unholding and disconnecting this {@code StreamingCall}.
* @param state The current streaming state of the call.
*/
- public void setStreamingState(@StreamingCallState int state) {
+ public void requestStreamingState(@StreamingCallState int state) {
mAdapter.setStreamingState(state);
}
}
diff --git a/telecomm/java/android/telecom/StreamingCallAdapter.java b/telecomm/java/android/telecom/StreamingCallAdapter.java
index bd8727d..54a3e24 100644
--- a/telecomm/java/android/telecom/StreamingCallAdapter.java
+++ b/telecomm/java/android/telecom/StreamingCallAdapter.java
@@ -25,7 +25,7 @@
* Telecom. When Telecom binds to a {@link CallStreamingService}, an instance of this class is given
* to the general streaming app through which it can manipulate the streaming calls. Whe the general
* streaming app is notified of new ongoing streaming calls, it can execute
- * {@link StreamingCall#setStreamingState(int)} for the ongoing streaming calls the user on the
+ * {@link StreamingCall#requestStreamingState(int)} for the ongoing streaming calls the user on the
* receiver side would like to hold, unhold and disconnect.
*
* @hide
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java
new file mode 100644
index 0000000..3e94f25
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.transparency.test;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
+ *
+ * <code> private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { public
+ * InstallMultiple() { super(getDevice(), null); } } </code>
+ */
+/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
+
+ private final ITestDevice mDevice;
+ private final IBuildInfo mBuild;
+
+ private final List<String> mArgs = new ArrayList<>();
+ private final Map<File, String> mFileToRemoteMap = new HashMap<>();
+
+ /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
+ mDevice = device;
+ mBuild = buildInfo;
+ addArg("-g");
+ }
+
+ T addArg(String arg) {
+ mArgs.add(arg);
+ return (T) this;
+ }
+
+ T addFile(String filename) throws FileNotFoundException {
+ return addFile(filename, filename);
+ }
+
+ T addFile(String filename, String remoteName) throws FileNotFoundException {
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
+ mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
+ return (T) this;
+ }
+
+ T inheritFrom(String packageName) {
+ addArg("-r");
+ addArg("-p " + packageName);
+ return (T) this;
+ }
+
+ void run() throws DeviceNotAvailableException {
+ run(true);
+ }
+
+ void runExpectingFailure() throws DeviceNotAvailableException {
+ run(false);
+ }
+
+ private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
+ final ITestDevice device = mDevice;
+
+ // Create an install session
+ final StringBuilder cmd = new StringBuilder();
+ cmd.append("pm install-create");
+ for (String arg : mArgs) {
+ cmd.append(' ').append(arg);
+ }
+
+ String result = device.executeShellCommand(cmd.toString());
+ TestCase.assertTrue(result, result.startsWith("Success"));
+
+ final int start = result.lastIndexOf("[");
+ final int end = result.lastIndexOf("]");
+ int sessionId = -1;
+ try {
+ if (start != -1 && end != -1 && start < end) {
+ sessionId = Integer.parseInt(result.substring(start + 1, end));
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Failed to parse install session: " + result);
+ }
+ if (sessionId == -1) {
+ throw new IllegalStateException("Failed to create install session: " + result);
+ }
+
+ // Push our files into session. Ideally we'd use stdin streaming,
+ // but ddmlib doesn't support it yet.
+ for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
+ final File file = entry.getKey();
+ final String remoteName = entry.getValue();
+ final String remotePath = "/data/local/tmp/" + file.getName();
+ if (!device.pushFile(file, remotePath)) {
+ throw new IllegalStateException("Failed to push " + file);
+ }
+
+ cmd.setLength(0);
+ cmd.append("pm install-write");
+ cmd.append(' ').append(sessionId);
+ cmd.append(' ').append(remoteName);
+ cmd.append(' ').append(remotePath);
+
+ result = device.executeShellCommand(cmd.toString());
+ TestCase.assertTrue(result, result.startsWith("Success"));
+ }
+
+ // Everything staged; let's pull trigger
+ cmd.setLength(0);
+ cmd.append("pm install-commit");
+ cmd.append(' ').append(sessionId);
+
+ result = device.executeShellCommand(cmd.toString());
+ if (expectingSuccess) {
+ TestCase.assertTrue(result, result.contains("Success"));
+ } else {
+ TestCase.assertFalse(result, result.contains("Success"));
+ }
+ }
+}
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 6fe548f..b8e9a17 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -81,6 +82,28 @@
}
@Test
+ public void testCollectAllSilentInstalledMbaInfo() throws Exception {
+ try {
+ new InstallMultiple()
+ .addFile("ApkVerityTestApp.apk")
+ .addFile("ApkVerityTestAppSplit.apk")
+ .run();
+ updatePreloadApp();
+ assertNotNull(getDevice().getAppPackageInfo("com.android.apkverity"));
+ assertNotNull(getDevice().getAppPackageInfo("com.android.egg"));
+
+ assertTrue(getDevice().setProperty("debug.transparency.bg-install-apps",
+ "com.android.apkverity,com.android.egg"));
+ runDeviceTest("testCollectAllSilentInstalledMbaInfo");
+ } finally {
+ // No need to wait until job complete, since we can't verifying very meaningfully.
+ cancelPendingJob();
+ uninstallPackage("com.android.apkverity");
+ uninstallPackage("com.android.egg");
+ }
+ }
+
+ @Test
public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
try {
installRebootlessApex();
@@ -171,4 +194,13 @@
result = getDevice().executeShellV2Command("pm install " + path);
assertTrue(result.getStatus() == CommandStatus.SUCCESS);
}
+
+ private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+ InstallMultiple() {
+ super(getDevice(), getBuild());
+ // Needed since in getMockBackgroundInstalledPackages, getPackageInfo runs as the caller
+ // uid. This also makes it consistent with installPackage's behavior.
+ addArg("--force-queryable");
+ }
+ }
}
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index 176bc28e..c087a85 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -36,6 +36,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HexFormat;
+import java.util.Set;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
@@ -111,4 +112,35 @@
assertThat(updatedPreload.mbaStatus).isEqualTo(/* MBA_STATUS_UPDATED_PRELOAD */ 2);
assertThat(updatedPreload.signerDigests).asList().containsNoneOf(null, "");
}
+
+ @Test
+ public void testCollectAllSilentInstalledMbaInfo() {
+ // Action
+ var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle());
+
+ // Verify
+ assertThat(appInfoList).isNotEmpty(); // because we just installed from the host side
+
+ var expectedAppNames = Set.of("com.android.apkverity", "com.android.egg");
+ var actualAppNames = appInfoList.stream().map((appInfo) -> appInfo.packageName)
+ .collect(Collectors.toList());
+ assertThat(actualAppNames).containsAtLeastElementsIn(expectedAppNames);
+
+ var actualSplitNames = new ArrayList<String>();
+ for (var appInfo : appInfoList) {
+ Log.d(TAG, "Received " + appInfo.packageName + " as a silent install");
+ if (expectedAppNames.contains(appInfo.packageName)) {
+ assertThat(appInfo.longVersion).isGreaterThan(0);
+ assertThat(appInfo.digestAlgorithm).isGreaterThan(0);
+ assertThat(appInfo.digest).isNotEmpty();
+ assertThat(appInfo.mbaStatus).isEqualTo(/* MBA_STATUS_NEW_INSTALL */ 3);
+ assertThat(appInfo.signerDigests).asList().containsNoneOf(null, "");
+
+ if (appInfo.splitName != null) {
+ actualSplitNames.add(appInfo.splitName);
+ }
+ }
+ }
+ assertThat(actualSplitNames).containsExactly("feature_x"); // Name of ApkVerityTestAppSplit
+ }
}
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
index 44112fc..3ea9651 100644
--- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
@@ -50,16 +50,13 @@
public class AttachedChoreographerTest {
private static final String TAG = "AttachedChoreographerTest";
private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
- private static final int THRESHOLD_MS = 10;
- private static final int CALLBACK_TIME_10_FPS = 100;
- private static final int CALLBACK_TIME_30_FPS = 33;
+ private static final long THRESHOLD_MS = 10;
private static final int FRAME_ITERATIONS = 21;
private static final int CALLBACK_MISSED_THRESHOLD = 2;
private final CountDownLatch mTestCompleteSignal = new CountDownLatch(2);
private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1);
private final CountDownLatch mNoCallbackSignal = new CountDownLatch(1);
- private final CountDownLatch mFramesSignal = new CountDownLatch(FRAME_ITERATIONS);
private ActivityScenario<GraphicsActivity> mScenario;
private int mInitialMatchContentFrameRate;
@@ -73,29 +70,27 @@
public void setUp() throws Exception {
mScenario = ActivityScenario.launch(GraphicsActivity.class);
mScenario.moveToState(Lifecycle.State.CREATED);
- mCallbackMissedCounter = 0;
mScenario.onActivity(activity -> {
mSurfaceView = activity.findViewById(R.id.surface);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- }
-
- @Override
public void surfaceCreated(SurfaceHolder holder) {
mSurfaceCreationCountDown.countDown();
}
@Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ }
+
+ @Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
});
-
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
uiDevice.wakeUp();
uiDevice.executeShellCommand("wm dismiss-keyguard");
@@ -131,7 +126,7 @@
}
@Test
- public void test_create_choreographer() {
+ public void testCreateChoreographer() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -166,7 +161,7 @@
}
@Test
- public void test_copy_surface_control() {
+ public void testCopySurfaceControl() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -199,7 +194,7 @@
}
@Test
- public void test_mirror_surface_control() {
+ public void testMirrorSurfaceControl() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -231,7 +226,7 @@
}
@Test
- public void test_postFrameCallback() {
+ public void testPostFrameCallback() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -256,7 +251,7 @@
}
@Test
- public void test_postFrameCallbackDelayed() {
+ public void testPostFrameCallbackDelayed() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -283,7 +278,7 @@
}
@Test
- public void test_postCallback() {
+ public void testPostCallback() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -309,7 +304,7 @@
}
@Test
- public void test_postCallbackDelayed() {
+ public void testPostCallbackDelayed() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -335,7 +330,7 @@
}
@Test
- public void test_postVsyncCallback() {
+ public void testPostVsyncCallback() {
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeout */ 1L)) {
fail("Unable to create surface within 1 Second");
@@ -359,52 +354,37 @@
}
@Test
- public void test_choreographer_10Hz_refreshRate() {
- mScenario.onActivity(activity -> {
- if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
- fail("Unable to create surface within 1 Second");
+ public void testChoreographerDivisorRefreshRate() {
+ for (int divisor : new int[]{2, 3}) {
+ CountDownLatch continueLatch = new CountDownLatch(1);
+ mScenario.onActivity(activity -> {
+ if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+ fail("Unable to create surface within 1 Second");
+ }
+ SurfaceControl sc = mSurfaceView.getSurfaceControl();
+ Choreographer choreographer = sc.getChoreographer();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+ float fps = displayRefreshRate / divisor;
+ long callbackDurationMs = Math.round(1000 / fps);
+ mCallbackMissedCounter = 0;
+ transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+ .addTransactionCommittedListener(Runnable::run,
+ () -> verifyVsyncCallbacks(choreographer,
+ callbackDurationMs, continueLatch, FRAME_ITERATIONS))
+ .apply();
+ });
+ // wait for the previous callbacks to finish before moving to the next divisor
+ if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
+ fail("Test not finished in 5 Seconds");
}
- SurfaceControl sc = mSurfaceView.getSurfaceControl();
- Choreographer choreographer = sc.getChoreographer();
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- transaction.setFrameRate(sc, 10.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
- .addTransactionCommittedListener(Runnable::run,
- () -> verifyVsyncCallbacks(choreographer,
- CALLBACK_TIME_10_FPS))
- .apply();
- mTestCompleteSignal.countDown();
- });
- if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) {
- fail("Test not finished in 5 Seconds");
}
}
- @Test
- public void test_choreographer_30Hz_refreshRate() {
- mScenario.onActivity(activity -> {
- if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
- fail("Unable to create surface within 1 Second");
- }
- SurfaceControl sc = mSurfaceView.getSurfaceControl();
- Choreographer choreographer = sc.getChoreographer();
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- transaction.setFrameRate(sc, 30.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
- .addTransactionCommittedListener(Runnable::run,
- () -> verifyVsyncCallbacks(choreographer,
- CALLBACK_TIME_30_FPS))
- .apply();
- mTestCompleteSignal.countDown();
- });
- if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) {
- fail("Test not finished in 5 Seconds");
- }
- }
-
- private void verifyVsyncCallbacks(Choreographer choreographer, int callbackDurationMs) {
+ private void verifyVsyncCallbacks(Choreographer choreographer, long callbackDurationMs,
+ CountDownLatch continueLatch, int frameCount) {
long callbackRequestedTimeNs = System.nanoTime();
choreographer.postVsyncCallback(frameData -> {
- mFramesSignal.countDown();
- final long frameCount = mFramesSignal.getCount();
if (frameCount > 0) {
if (!mIsFirstCallback) {
// Skip the first callback as it takes 1 frame
@@ -422,18 +402,19 @@
}
}
mIsFirstCallback = false;
- verifyVsyncCallbacks(choreographer, callbackDurationMs);
+ verifyVsyncCallbacks(choreographer, callbackDurationMs,
+ continueLatch, frameCount - 1);
} else {
assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while "
+ CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed",
mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD);
- mTestCompleteSignal.countDown();
+ continueLatch.countDown();
}
});
}
private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs,
- int expectedCallbackMs) {
+ long expectedCallbackMs) {
long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs)
- TimeUnit.NANOSECONDS.toMillis(requestedTimeNs);
return Math.abs(expectedCallbackMs - actualTimeMs);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index f26ce25..8d2c643 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -259,7 +259,7 @@
snapshotLayers
.mapNotNull { snapshotLayer -> snapshotLayer.layer?.visibleRegion }
.toTypedArray()
- val snapshotRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
+ val snapshotRegion = RegionSubject(visibleAreas, this, timestamp)
val appVisibleRegion = it.visibleRegion(component)
if (snapshotRegion.region.isNotEmpty) {
snapshotRegion.coversExactly(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
similarity index 63%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
index ea67729..7f2e057f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -33,13 +33,13 @@
* Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
* split.
*
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplit`
+ * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplit(flicker: FlickerTest) :
+class OpenActivityEmbeddingPlaceholderSplitTest(flicker: FlickerTest) :
ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
@@ -55,9 +55,21 @@
}
}
+ /** Main activity should become invisible after launching the placeholder primary activity. */
@Presubmit
@Test
- fun mainActivityBecomesInvisible() {
+ fun mainActivityWindowBecomesInvisible() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Main activity should become invisible after launching the placeholder primary activity. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesInvisible() {
flicker.assertLayers {
isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
@@ -65,76 +77,41 @@
}
}
+ /**
+ * Placeholder primary and secondary should become visible after launch. The windows are not
+ * necessarily to become visible at the same time.
+ */
@Presubmit
@Test
- fun placeholderSplitBecomesVisible() {
- flicker.assertLayers {
- isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ fun placeholderSplitWindowsBecomeVisible() {
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
}
- flicker.assertLayers {
- isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
}
}
- /** {@inheritDoc} */
- @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
+ /** Placeholder primary and secondary should become visible together after launch. */
@Presubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() =
- super.statusBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ fun placeholderSplitLayersBecomeVisible() {
+ flicker.assertLayers {
+ isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ }
+ }
companion object {
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
new file mode 100644
index 0000000..5fff15c
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening a secondary activity that will split with the main activity.
+ *
+ * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenActivityEmbeddingSecondaryToSplitTest(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions { testApp.launchSecondaryActivity(wmHelper) }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Main activity should remain visible when enter split from fullscreen. */
+ @Presubmit
+ @Test
+ fun mainActivityWindowIsAlwaysVisible() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /**
+ * Main activity surface is animated from fullscreen to ActivityEmbedding split.
+ * During the transition, there is a period of time that it is covered by a snapshot of itself.
+ */
+ @Presubmit
+ @Test
+ fun mainActivityLayerIsAlwaysVisible() {
+ flicker.assertLayers {
+ isVisible(
+ ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
+ ComponentNameMatcher.TRANSITION_SNAPSHOT
+ )
+ )
+ }
+ flicker.assertLayersEnd {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
+ }
+ }
+
+ /** Secondary activity should become visible after launching into split. */
+ @Presubmit
+ @Test
+ fun secondaryActivityWindowBecomesVisible() {
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity should become visible after launching into split. */
+ @Presubmit
+ @Test
+ fun secondaryActivityLayerBecomesVisible() {
+ flicker.assertLayers {
+ isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 368cc56..a2db587 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -39,6 +39,25 @@
) : StandardAppHelper(instr, launcherName, component) {
/**
+ * Clicks the button to launch the secondary activity, which should split with the main activity
+ * based on the split pair rule.
+ */
+ fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "launch_secondary_activity_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) { "Can't find launch secondary activity button on screen." }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, STATE_RESUMED)
+ .waitForAndVerify()
+ }
+
+ /**
* Clicks the button to launch the placeholder primary activity, which should launch the
* placeholder secondary activity based on the placeholder rule.
*/
@@ -63,6 +82,9 @@
val MAIN_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
+ val SECONDARY_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
+
val PLACEHOLDER_PRIMARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
.toFlickerComponent()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 0f59d81..ca0d571 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -90,7 +90,7 @@
imeSnapshotLayer.layer?.visibleRegion
}
.toTypedArray()
- val imeVisibleRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
+ val imeVisibleRegion = RegionSubject(visibleAreas, this, timestamp)
val appVisibleRegion = it.visibleRegion(imeTestApp)
if (imeVisibleRegion.region.isNotEmpty) {
imeVisibleRegion.coversAtMost(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index d76c94d..dbf5959 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -111,9 +111,9 @@
val appWindow = it.windowState(testApp.`package`)
val flags = appWindow.windowState?.attributes?.flags ?: 0
appWindow
- .verify("isFullScreen")
+ .check { "isFullScreen" }
.that(flags.and(WindowManager.LayoutParams.FLAG_FULLSCREEN))
- .isGreaterThan(0)
+ .isGreater(0)
}
}
}
@@ -127,13 +127,13 @@
val appWindow = it.windowState(testApp.`package`)
val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0
appWindow
- .verify("isRotationSeamless")
+ .check { "isRotationSeamless" }
.that(
rotationAnimation.and(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
)
)
- .isGreaterThan(0)
+ .isGreater(0)
}
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index cd47f60..5361d73f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -179,6 +179,13 @@
</intent-filter>
</activity>
<activity
+ android:name=".ActivityEmbeddingSecondaryActivity"
+ android:label="ActivityEmbedding Secondary"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="false"/>
+ <activity
android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
android:label="ActivityEmbedding Placeholder Primary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 19c81a8..d78b9a8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -22,6 +22,14 @@
android:background="@android:color/holo_orange_light">
<Button
+ android:id="@+id/launch_secondary_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:onClick="launchSecondaryActivity"
+ android:text="Launch Secondary Activity" />
+
+ <Button
android:id="@+id/launch_placeholder_split_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 04a590d..6a7a2cc 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -25,6 +25,7 @@
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.EmbeddingRule;
+import androidx.window.extensions.embedding.SplitPairRule;
import androidx.window.extensions.embedding.SplitPlaceholderRule;
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
@@ -40,20 +41,23 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_embedding_main_layout);
+ }
- initializeSplitRules();
+ /** R.id.launch_secondary_activity_button onClick */
+ public void launchSecondaryActivity(View view) {
+ initializeSplitRules(createSplitPairRules());
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
}
/** R.id.launch_placeholder_split_button onClick */
public void launchPlaceholderSplit(View view) {
- startActivity(
- new Intent().setComponent(
- ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
- )
- );
+ initializeSplitRules(createSplitPlaceholderRules());
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT));
}
- private void initializeSplitRules() {
+ private void initializeSplitRules(Set<EmbeddingRule> rules) {
ActivityEmbeddingComponent embeddingComponent =
ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
if (embeddingComponent == null) {
@@ -62,14 +66,28 @@
finish();
return;
}
-
- embeddingComponent.setEmbeddingRules(getSplitRules());
+ embeddingComponent.setEmbeddingRules(rules);
}
- private Set<EmbeddingRule> getSplitRules() {
+ private Set<EmbeddingRule> createSplitPairRules() {
final Set<EmbeddingRule> rules = new ArraySet<>();
+ final SplitPairRule rule = new SplitPairRule.Builder(
+ activitiesPair -> activitiesPair.first instanceof ActivityEmbeddingMainActivity
+ && activitiesPair.second instanceof ActivityEmbeddingSecondaryActivity,
+ activityIntentPair ->
+ activityIntentPair.first instanceof ActivityEmbeddingMainActivity
+ && activityIntentPair.second.getComponent().equals(ActivityOptions
+ .ActivityEmbedding.SecondaryActivity.COMPONENT),
+ windowMetrics -> true)
+ .setSplitRatio(DEFAULT_SPLIT_RATIO)
+ .build();
+ rules.add(rule);
+ return rules;
+ }
- final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
+ private Set<EmbeddingRule> createSplitPlaceholderRules() {
+ final Set<EmbeddingRule> rules = new ArraySet<>();
+ final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(
new Intent().setComponent(
ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
@@ -78,7 +96,7 @@
windowMetrics -> true)
.setSplitRatio(DEFAULT_SPLIT_RATIO)
.build();
- rules.add(placeholderRule);
+ rules.add(rule);
return rules;
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
new file mode 100644
index 0000000..00f4c25
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.graphics.Color;
+
+/**
+ * Activity to be used as the secondary activity to split with
+ * {@link ActivityEmbeddingMainActivity}.
+ */
+public class ActivityEmbeddingSecondaryActivity extends ActivityEmbeddingBaseActivity {
+ @Override
+ int getBackgroundColor() {
+ return Color.YELLOW;
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 00684de..b61bc0c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -87,6 +87,12 @@
FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity");
}
+ public static class SecondaryActivity {
+ public static final String LABEL = "ActivityEmbeddingSecondaryActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
+ }
+
public static class PlaceholderPrimaryActivity {
public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
index b67dc380..7a8d949 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -15,7 +15,7 @@
*/
package com.android.test
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import org.junit.Test
@@ -37,7 +37,7 @@
1000 /* ms */))
}
- assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames)
}
@Test
@@ -51,7 +51,7 @@
assertEquals(0, activity.mSurfaceProxy.waitUntilBufferDisplayed(2, 5000 /* ms */))
}
- assertThat(trace).hasFrameSequence("SurfaceView", 1..2L)
+ LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..2L)
}
@Test
@@ -69,7 +69,7 @@
5000 /* ms */))
}
- assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames)
}
@Test
@@ -92,7 +92,7 @@
5000 /* ms */))
}
- assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames)
}
@Test
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
index e9e0246..da53387 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
@@ -16,7 +16,7 @@
package com.android.test
import android.graphics.Point
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
import junit.framework.Assert.assertEquals
@@ -45,10 +45,10 @@
activity.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */)
}
// Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
- assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+ LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist()
// Verify the next buffer is submitted with the correct size
- assertThat(trace).layer("SurfaceView", 3).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 3).also {
it.hasBufferSize(defaultBufferSize)
// scaling mode is not passed down to the layer for blast
if (useBlastAdapter) {
@@ -81,9 +81,9 @@
}
// verify buffer size is reset to default buffer size
- assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
- assertThat(trace).layer("SurfaceView", 2).doesNotExist()
- assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist()
+ LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
}
@Test
@@ -109,10 +109,11 @@
}
// verify buffer size is reset to default buffer size
- assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
- assertThat(trace).layer("SurfaceView", 2).doesNotExist()
- assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
- assertThat(trace).layer("SurfaceView", 3).hasBufferOrientation(Transform.ROT_90.value)
+ LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist()
+ LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 3)
+ .hasBufferOrientation(Transform.ROT_90.value)
}
@Test
@@ -141,11 +142,11 @@
}
for (count in 0 until 5) {
- assertThat(trace).layer("SurfaceView", (count * 3) + 1L)
+ LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 1L)
.hasBufferSize(defaultBufferSize)
- assertThat(trace).layer("SurfaceView", (count * 3) + 2L)
+ LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 2L)
.doesNotExist()
- assertThat(trace).layer("SurfaceView", (count * 3) + 3L)
+ LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 3L)
.hasBufferSize(bufferSize)
}
}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
index 0802990..2d6c664 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
@@ -19,7 +19,7 @@
import android.graphics.Point
import android.graphics.Rect
import android.os.SystemClock
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
import junit.framework.Assert.assertEquals
@@ -43,7 +43,7 @@
}
// verify buffer size is reset to default buffer size
- assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
}
@Test
@@ -56,7 +56,7 @@
activity.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
}
- assertThat(trace).layer("SurfaceView", 1).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 1).also {
it.hasBufferSize(bufferSize)
it.hasLayerSize(defaultBufferSize)
it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
@@ -73,7 +73,7 @@
activity.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
}
- assertThat(trace).layer("SurfaceView", 1).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 1).also {
it.hasBufferSize(bufferSize)
it.hasLayerSize(defaultBufferSize)
it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
@@ -102,9 +102,9 @@
}
// verify buffer size is reset to default buffer size
- assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
- assertThat(trace).layer("SurfaceView", 2).doesNotExist()
- assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist()
+ LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
}
@Test
@@ -118,7 +118,7 @@
activity.mSurfaceProxy.waitUntilBufferDisplayed(index + 1L, 500 /* ms */)
}
- assertThat(trace).layer("SurfaceView", index + 1L).also {
+ LayersTraceSubject(trace).layer("SurfaceView", index + 1L).also {
it.hasBufferSize(defaultBufferSize)
it.hasLayerSize(defaultBufferSize)
it.hasBufferOrientation(transform.value)
@@ -145,7 +145,7 @@
}
// check that the layer and buffer starts with the default size
- assertThat(trace).layer("SurfaceView", 1).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 1).also {
it.hasBufferSize(defaultBufferSize)
it.hasLayerSize(defaultBufferSize)
}
@@ -169,7 +169,7 @@
checkPixels(svBounds, Color.BLUE)
}
- assertThat(trace).layer("SurfaceView", 1).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 1).also {
it.hasLayerSize(newSize)
it.hasBufferSize(defaultBufferSize)
}
@@ -193,7 +193,7 @@
}
// check that the layer and buffer starts with the default size
- assertThat(trace).layer("SurfaceView", 1).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 1).also {
it.hasBufferSize(defaultBufferSize)
it.hasLayerSize(defaultBufferSize)
}
@@ -216,7 +216,7 @@
checkPixels(svBounds, Color.BLUE)
}
- assertThat(trace).layer("SurfaceView", 1).also {
+ LayersTraceSubject(trace).layer("SurfaceView", 1).also {
it.hasLayerSize(defaultBufferSize)
it.hasBufferSize(defaultBufferSize)
}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
index 69012bd..cf4186d 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
@@ -16,7 +16,7 @@
package com.android.test
import android.graphics.Point
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
import junit.framework.Assert.assertEquals
import org.junit.Assume.assumeFalse
@@ -69,8 +69,8 @@
}
// verify buffer size is reset to default buffer size
- assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
- assertThat(trace).layer("SurfaceView", 2).doesNotExist()
- assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist()
+ LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
}
}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
index ee41d79..61d4095 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
@@ -17,7 +17,7 @@
import android.graphics.Color
import android.graphics.Rect
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import junit.framework.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,7 +39,7 @@
}
}
- assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames)
}
/** Submit buffers as fast as possible testing that we are not blocked when dequeuing the buffer
@@ -57,7 +57,7 @@
5000 /* ms */))
}
- assertThat(trace).hasFrameSequence("SurfaceView", numFrames..numFrames)
+ LayersTraceSubject(trace).hasFrameSequence("SurfaceView", numFrames..numFrames)
}
/** Keep overwriting the buffer without queuing buffers and check that we present the latest
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
index 03b43cc..722e671 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
@@ -23,7 +23,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.server.wm.flicker.monitor.LayersTraceMonitor
import com.android.server.wm.flicker.monitor.withSFTracing
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import org.junit.After
import org.junit.Before
import org.junit.FixMethodOrder
@@ -90,13 +90,13 @@
secondBounds.offsetTo(0, 0)
// verify buffer size should be changed to expected values.
- assertThat(trace).layer(FIRST_ACTIVITY, frame.toLong()).also {
+ LayersTraceSubject(trace).layer(FIRST_ACTIVITY, frame.toLong()).also {
val firstTaskSize = Point(firstBounds.width(), firstBounds.height())
it.hasLayerSize(firstTaskSize)
it.hasBufferSize(firstTaskSize)
}
- assertThat(trace).layer(SECOND_ACTIVITY, frame.toLong()).also {
+ LayersTraceSubject(trace).layer(SECOND_ACTIVITY, frame.toLong()).also {
val secondTaskSize = Point(secondBounds.width(), secondBounds.height())
it.hasLayerSize(secondTaskSize)
it.hasBufferSize(secondTaskSize)
diff --git a/wifi/tests/src/android/net/wifi/OWNERS b/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS
similarity index 100%
rename from wifi/tests/src/android/net/wifi/OWNERS
rename to wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS