Merge "Promote tests to presubmit."
diff --git a/core/api/current.txt b/core/api/current.txt
index b42face..164e14a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -143,6 +143,7 @@
field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
field public static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
+ field public static final String READ_MEDIA_VISUAL_USER_SELECTED = "android.permission.READ_MEDIA_VISUAL_USER_SELECTED";
field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -41762,6 +41763,7 @@
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array";
+ field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
@@ -44069,6 +44071,7 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index dcbe7ed..352b4f9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -587,6 +587,7 @@
field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
+ field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
@@ -15924,10 +15925,17 @@
package android.view.accessibility {
+ public abstract class AccessibilityDisplayProxy {
+ ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
+ method public int getDisplayId();
+ }
+
public final class AccessibilityManager {
method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean registerDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean unregisterDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9c1a55b..cdb1510 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3157,7 +3157,7 @@
}
public final class InputMethodManager {
- method public void addVirtualStylusIdForTestSession();
+ method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
method public int getDisplayId();
method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1b972e0..267e5b6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1360,9 +1360,17 @@
*/
public static final int OP_RUN_LONG_JOBS = AppProtoEnums.APP_OP_RUN_LONG_JOBS;
+ /**
+ * Notify apps that they have been granted URI permission photos
+ *
+ * @hide
+ */
+ public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
+ AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 123;
+ public static final int _NUM_OP = 124;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1833,6 +1841,14 @@
@SystemApi
public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO =
"android:receive_ambient_trigger_audio";
+ /**
+ * Notify apps that they have been granted URI permission photos
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED =
+ "android:read_media_visual_user_selected";
/**
* Record audio from near-field microphone (ie. TV remote)
@@ -1948,6 +1964,7 @@
OP_MANAGE_MEDIA,
OP_TURN_SCREEN_ON,
OP_RUN_LONG_JOBS,
+ OP_READ_MEDIA_VISUAL_USER_SELECTED,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2329,7 +2346,11 @@
"RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS")
- .setPermission(Manifest.permission.RUN_LONG_JOBS).build()
+ .setPermission(Manifest.permission.RUN_LONG_JOBS).build(),
+ new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
+ OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
+ .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
};
/**
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
new file mode 100644
index 0000000..9cc9c72
--- /dev/null
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-provider metadata and entries for the create-credential flow.
+ *
+ * @hide
+ */
+public class CreateCredentialProviderData extends ProviderData implements Parcelable {
+ @NonNull
+ private final List<Entry> mSaveEntries;
+ @NonNull
+ private final List<Entry> mActionChips;
+ private final boolean mIsDefaultProvider;
+ @Nullable
+ private final Entry mRemoteEntry;
+
+ public CreateCredentialProviderData(
+ @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
+ @NonNull List<Entry> actionChips, boolean isDefaultProvider,
+ @Nullable Entry remoteEntry) {
+ super(providerFlattenedComponentName);
+ mSaveEntries = saveEntries;
+ mActionChips = actionChips;
+ mIsDefaultProvider = isDefaultProvider;
+ mRemoteEntry = remoteEntry;
+ }
+
+ @NonNull
+ public List<Entry> getSaveEntries() {
+ return mSaveEntries;
+ }
+
+ @NonNull
+ public List<Entry> getActionChips() {
+ return mActionChips;
+ }
+
+ public boolean isDefaultProvider() {
+ return mIsDefaultProvider;
+ }
+
+ @Nullable
+ public Entry getRemoteEntry() {
+ return mRemoteEntry;
+ }
+
+ protected CreateCredentialProviderData(@NonNull Parcel in) {
+ super(in);
+
+ List<Entry> credentialEntries = new ArrayList<>();
+ in.readTypedList(credentialEntries, Entry.CREATOR);
+ mSaveEntries = credentialEntries;
+ AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
+
+ List<Entry> actionChips = new ArrayList<>();
+ in.readTypedList(actionChips, Entry.CREATOR);
+ mActionChips = actionChips;
+ AnnotationValidations.validate(NonNull.class, null, mActionChips);
+
+ mIsDefaultProvider = in.readBoolean();
+
+ Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
+ mRemoteEntry = remoteEntry;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeTypedList(mSaveEntries);
+ dest.writeTypedList(mActionChips);
+ dest.writeBoolean(isDefaultProvider());
+ dest.writeTypedObject(mRemoteEntry, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<CreateCredentialProviderData> CREATOR =
+ new Creator<CreateCredentialProviderData>() {
+ @Override
+ public CreateCredentialProviderData createFromParcel(@NonNull Parcel in) {
+ return new CreateCredentialProviderData(in);
+ }
+
+ @Override
+ public CreateCredentialProviderData[] newArray(int size) {
+ return new CreateCredentialProviderData[size];
+ }
+ };
+
+ /**
+ * Builder for {@link CreateCredentialProviderData}.
+ *
+ * @hide
+ */
+ public static class Builder {
+ private @NonNull String mProviderFlattenedComponentName;
+ private @NonNull List<Entry> mSaveEntries = new ArrayList<>();
+ private @NonNull List<Entry> mActionChips = new ArrayList<>();
+ private boolean mIsDefaultProvider = false;
+ private @Nullable Entry mRemoteEntry = null;
+
+ /** Constructor with required properties. */
+ public Builder(@NonNull String providerFlattenedComponentName) {
+ mProviderFlattenedComponentName = providerFlattenedComponentName;
+ }
+
+ /** Sets the list of save credential entries to be displayed to the user. */
+ @NonNull
+ public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+ mSaveEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the list of action chips to be displayed to the user. */
+ @NonNull
+ public Builder setActionChips(@NonNull List<Entry> actionChips) {
+ mActionChips = actionChips;
+ return this;
+ }
+
+ /** Sets whether this provider is the user's selected default provider. */
+ @NonNull
+ public Builder setIsDefaultProvider(boolean isDefaultProvider) {
+ mIsDefaultProvider = isDefaultProvider;
+ return this;
+ }
+
+ /** Builds a {@link CreateCredentialProviderData}. */
+ @NonNull
+ public CreateCredentialProviderData build() {
+ return new CreateCredentialProviderData(mProviderFlattenedComponentName,
+ mSaveEntries, mActionChips, mIsDefaultProvider, mRemoteEntry);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java
new file mode 100644
index 0000000..73c8dbe
--- /dev/null
+++ b/core/java/android/credentials/ui/DisabledProviderData.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Metadata of a disabled provider.
+ *
+ * @hide
+ */
+public class DisabledProviderData extends ProviderData implements Parcelable {
+
+ public DisabledProviderData(
+ @NonNull String providerFlattenedComponentName) {
+ super(providerFlattenedComponentName);
+ }
+
+ protected DisabledProviderData(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() {
+ @Override
+ public DisabledProviderData createFromParcel(@NonNull Parcel in) {
+ return new DisabledProviderData(in);
+ }
+
+ @Override
+ public DisabledProviderData[] newArray(int size) {
+ return new DisabledProviderData[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
new file mode 100644
index 0000000..834f9825
--- /dev/null
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-provider metadata and entries for the get-credential flow.
+ *
+ * @hide
+ */
+public class GetCredentialProviderData extends ProviderData implements Parcelable {
+ @NonNull
+ private final List<Entry> mCredentialEntries;
+ @NonNull
+ private final List<Entry> mActionChips;
+ @Nullable
+ private final Entry mAuthenticationEntry;
+ @Nullable
+ private final Entry mRemoteEntry;
+
+ public GetCredentialProviderData(
+ @NonNull String providerFlattenedComponentName, @NonNull List<Entry> credentialEntries,
+ @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
+ @Nullable Entry remoteEntry) {
+ super(providerFlattenedComponentName);
+ mCredentialEntries = credentialEntries;
+ mActionChips = actionChips;
+ mAuthenticationEntry = authenticationEntry;
+ mRemoteEntry = remoteEntry;
+ }
+
+ @NonNull
+ public List<Entry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ @NonNull
+ public List<Entry> getActionChips() {
+ return mActionChips;
+ }
+
+ @Nullable
+ public Entry getAuthenticationEntry() {
+ return mAuthenticationEntry;
+ }
+
+ @Nullable
+ public Entry getRemoteEntry() {
+ return mRemoteEntry;
+ }
+
+ protected GetCredentialProviderData(@NonNull Parcel in) {
+ super(in);
+
+ List<Entry> credentialEntries = new ArrayList<>();
+ in.readTypedList(credentialEntries, Entry.CREATOR);
+ mCredentialEntries = credentialEntries;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
+
+ List<Entry> actionChips = new ArrayList<>();
+ in.readTypedList(actionChips, Entry.CREATOR);
+ mActionChips = actionChips;
+ AnnotationValidations.validate(NonNull.class, null, mActionChips);
+
+ Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
+ mAuthenticationEntry = authenticationEntry;
+
+ Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
+ mRemoteEntry = remoteEntry;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeTypedList(mCredentialEntries);
+ dest.writeTypedList(mActionChips);
+ dest.writeTypedObject(mAuthenticationEntry, flags);
+ dest.writeTypedObject(mRemoteEntry, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
+ new Creator<GetCredentialProviderData>() {
+ @Override
+ public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
+ return new GetCredentialProviderData(in);
+ }
+
+ @Override
+ public GetCredentialProviderData[] newArray(int size) {
+ return new GetCredentialProviderData[size];
+ }
+ };
+
+ /**
+ * Builder for {@link GetCredentialProviderData}.
+ *
+ * @hide
+ */
+ public static class Builder {
+ private @NonNull String mProviderFlattenedComponentName;
+ private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
+ private @NonNull List<Entry> mActionChips = new ArrayList<>();
+ private @Nullable Entry mAuthenticationEntry = null;
+ private @Nullable Entry mRemoteEntry = null;
+
+ /** Constructor with required properties. */
+ public Builder(@NonNull String providerFlattenedComponentName) {
+ mProviderFlattenedComponentName = providerFlattenedComponentName;
+ }
+
+ /** Sets the list of save / get credential entries to be displayed to the user. */
+ @NonNull
+ public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+ mCredentialEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the list of action chips to be displayed to the user. */
+ @NonNull
+ public Builder setActionChips(@NonNull List<Entry> actionChips) {
+ mActionChips = actionChips;
+ return this;
+ }
+
+ /** Sets the authentication entry to be displayed to the user. */
+ @NonNull
+ public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) {
+ mAuthenticationEntry = authenticationEntry;
+ return this;
+ }
+
+ /** Sets the remote entry to be displayed to the user. */
+ @NonNull
+ public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+ mRemoteEntry = remoteEntry;
+ return this;
+ }
+
+ /** Builds a {@link GetCredentialProviderData}. */
+ @NonNull
+ public GetCredentialProviderData build() {
+ return new GetCredentialProviderData(mProviderFlattenedComponentName,
+ mCredentialEntries, mActionChips, mAuthenticationEntry, mRemoteEntry);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 1b70ea4..4751696 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -30,15 +30,20 @@
*/
public class IntentFactory {
/** Generate a new launch intent to the . */
- public static Intent newIntent(RequestInfo requestInfo,
- ArrayList<ProviderData> providerDataList, ResultReceiver resultReceiver) {
+ public static Intent newIntent(
+ RequestInfo requestInfo,
+ ArrayList<ProviderData> enabledProviderDataList,
+ ArrayList<DisabledProviderData> disabledProviderDataList,
+ ResultReceiver resultReceiver) {
Intent intent = new Intent();
// TODO: define these as proper config strings.
String activityName = "com.android.credentialmanager/.CredentialSelectorActivity";
intent.setComponent(ComponentName.unflattenFromString(activityName));
intent.putParcelableArrayListExtra(
- ProviderData.EXTRA_PROVIDER_DATA_LIST, providerDataList);
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
+ intent.putParcelableArrayListExtra(
+ ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
intent.putExtra(Constants.EXTRA_RESULT_RECEIVER,
toIpcFriendlyResultReceiver(resultReceiver));
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 3728469..eeaeb46 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -16,232 +16,62 @@
package android.credentials.ui;
-import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
-import java.util.ArrayList;
-import java.util.List;
-
/**
- * Holds metadata and credential entries for a single provider.
+ * Super class for data structures that hold metadata and credential entries for a single provider.
*
* @hide
*/
-public class ProviderData implements Parcelable {
+public abstract class ProviderData implements Parcelable {
/**
- * The intent extra key for the list of {@code ProviderData} when launching the UX
- * activities.
+ * The intent extra key for the list of {@code ProviderData} from active providers when
+ * launching the UX activities.
*/
- public static final String EXTRA_PROVIDER_DATA_LIST =
- "android.credentials.ui.extra.PROVIDER_DATA_LIST";
+ public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST =
+ "android.credentials.ui.extra.ENABLED_PROVIDER_DATA_LIST";
+ /**
+ * The intent extra key for the list of {@code ProviderData} from disabled providers when
+ * launching the UX activities.
+ */
+ public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST =
+ "android.credentials.ui.extra.DISABLED_PROVIDER_DATA_LIST";
@NonNull
private final String mProviderFlattenedComponentName;
- @NonNull
- private final String mProviderDisplayName;
- @Nullable
- private final Icon mIcon;
- @NonNull
- private final List<Entry> mCredentialEntries;
- @NonNull
- private final List<Entry> mActionChips;
- @Nullable
- private final Entry mAuthenticationEntry;
-
- private final @CurrentTimeMillisLong long mLastUsedTimeMillis;
public ProviderData(
- @NonNull String providerFlattenedComponentName, @NonNull String providerDisplayName,
- @Nullable Icon icon, @NonNull List<Entry> credentialEntries,
- @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
- @CurrentTimeMillisLong long lastUsedTimeMillis) {
+ @NonNull String providerFlattenedComponentName) {
mProviderFlattenedComponentName = providerFlattenedComponentName;
- mProviderDisplayName = providerDisplayName;
- mIcon = icon;
- mCredentialEntries = credentialEntries;
- mActionChips = actionChips;
- mAuthenticationEntry = authenticationEntry;
- mLastUsedTimeMillis = lastUsedTimeMillis;
}
- /** Returns the unique provider id. */
+ /**
+ * Returns provider component name.
+ * It also serves as the unique identifier for this provider.
+ */
@NonNull
public String getProviderFlattenedComponentName() {
return mProviderFlattenedComponentName;
}
- @NonNull
- public String getProviderDisplayName() {
- return mProviderDisplayName;
- }
-
- @Nullable
- public Icon getIcon() {
- return mIcon;
- }
-
- @NonNull
- public List<Entry> getCredentialEntries() {
- return mCredentialEntries;
- }
-
- @NonNull
- public List<Entry> getActionChips() {
- return mActionChips;
- }
-
- @Nullable
- public Entry getAuthenticationEntry() {
- return mAuthenticationEntry;
- }
-
- /** Returns the time when the provider was last used. */
- public @CurrentTimeMillisLong long getLastUsedTimeMillis() {
- return mLastUsedTimeMillis;
- }
-
protected ProviderData(@NonNull Parcel in) {
String providerFlattenedComponentName = in.readString8();
mProviderFlattenedComponentName = providerFlattenedComponentName;
AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
-
- String providerDisplayName = in.readString8();
- mProviderDisplayName = providerDisplayName;
- AnnotationValidations.validate(NonNull.class, null, mProviderDisplayName);
-
- Icon icon = in.readTypedObject(Icon.CREATOR);
- mIcon = icon;
-
- List<Entry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, Entry.CREATOR);
- mCredentialEntries = credentialEntries;
- AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
-
- List<Entry> actionChips = new ArrayList<>();
- in.readTypedList(actionChips, Entry.CREATOR);
- mActionChips = actionChips;
- AnnotationValidations.validate(NonNull.class, null, mActionChips);
-
- Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
- mAuthenticationEntry = authenticationEntry;
-
- long lastUsedTimeMillis = in.readLong();
- mLastUsedTimeMillis = lastUsedTimeMillis;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mProviderFlattenedComponentName);
- dest.writeString8(mProviderDisplayName);
- dest.writeTypedObject(mIcon, flags);
- dest.writeTypedList(mCredentialEntries);
- dest.writeTypedList(mActionChips);
- dest.writeTypedObject(mAuthenticationEntry, flags);
- dest.writeLong(mLastUsedTimeMillis);
}
@Override
public int describeContents() {
return 0;
}
-
- public static final @NonNull Creator<ProviderData> CREATOR = new Creator<ProviderData>() {
- @Override
- public ProviderData createFromParcel(@NonNull Parcel in) {
- return new ProviderData(in);
- }
-
- @Override
- public ProviderData[] newArray(int size) {
- return new ProviderData[size];
- }
- };
-
- /**
- * Builder for {@link ProviderData}.
- *
- * @hide
- */
- public static class Builder {
- private @NonNull String mProviderFlattenedComponentName;
- private @NonNull String mProviderDisplayName;
- private @Nullable Icon mIcon;
- private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
- private @NonNull List<Entry> mActionChips = new ArrayList<>();
- private @Nullable Entry mAuthenticationEntry = null;
- private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L;
-
- /** Constructor with required properties. */
- public Builder(@NonNull String providerFlattenedComponentName,
- @NonNull String providerDisplayName,
- @Nullable Icon icon) {
- mProviderFlattenedComponentName = providerFlattenedComponentName;
- mProviderDisplayName = providerDisplayName;
- mIcon = icon;
- }
-
- /** Sets the unique provider id. */
- @NonNull
- public Builder setProviderFlattenedComponentName(@NonNull String providerFlattenedComponentName) {
- mProviderFlattenedComponentName = providerFlattenedComponentName;
- return this;
- }
-
- /** Sets the provider display name to be displayed to the user. */
- @NonNull
- public Builder setProviderDisplayName(@NonNull String providerDisplayName) {
- mProviderDisplayName = providerDisplayName;
- return this;
- }
-
- /** Sets the provider icon to be displayed to the user. */
- @NonNull
- public Builder setIcon(@NonNull Icon icon) {
- mIcon = icon;
- return this;
- }
-
- /** Sets the list of save / get credential entries to be displayed to the user. */
- @NonNull
- public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
- mCredentialEntries = credentialEntries;
- return this;
- }
-
- /** Sets the list of action chips to be displayed to the user. */
- @NonNull
- public Builder setActionChips(@NonNull List<Entry> actionChips) {
- mActionChips = actionChips;
- return this;
- }
-
- /** Sets the authentication entry to be displayed to the user. */
- @NonNull
- public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) {
- mAuthenticationEntry = authenticationEntry;
- return this;
- }
-
- /** Sets the time when the provider was last used. */
- @NonNull
- public Builder setLastUsedTimeMillis(@CurrentTimeMillisLong long lastUsedTimeMillis) {
- mLastUsedTimeMillis = lastUsedTimeMillis;
- return this;
- }
-
- /** Builds a {@link ProviderData}. */
- @NonNull
- public ProviderData build() {
- return new ProviderData(mProviderFlattenedComponentName, mProviderDisplayName,
- mIcon, mCredentialEntries,
- mActionChips, mAuthenticationEntry, mLastUsedTimeMillis);
- }
- }
}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 619b08e..59d5118 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -69,6 +69,7 @@
private final boolean mIsFirstUsage;
+ // TODO: change to package name
@NonNull
private final String mAppDisplayName;
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 36ac1a0..8a92135 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -533,7 +533,6 @@
mProgramType = in.readInt();
mPrimaryId = in.readTypedObject(Identifier.CREATOR);
mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
- Arrays.sort(mSecondaryIds);
if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
throw new IllegalArgumentException("secondaryIds list must not contain nulls");
}
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index b4010a4..0f7c9b6 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -111,6 +111,12 @@
*/
public static final @RequestFlags int FLAG_IME_SHOWING = 0x80;
+ /**
+ * Indicates whether autofill session should reset the fill dialog state.
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_RESET_FILL_DIALOG_STATE = 0x100;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -208,7 +214,8 @@
FLAG_PASSWORD_INPUT_TYPE,
FLAG_VIEW_NOT_FOCUSED,
FLAG_SUPPORTS_FILL_DIALOG,
- FLAG_IME_SHOWING
+ FLAG_IME_SHOWING,
+ FLAG_RESET_FILL_DIALOG_STATE
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -236,6 +243,8 @@
return "FLAG_SUPPORTS_FILL_DIALOG";
case FLAG_IME_SHOWING:
return "FLAG_IME_SHOWING";
+ case FLAG_RESET_FILL_DIALOG_STATE:
+ return "FLAG_RESET_FILL_DIALOG_STATE";
default: return Integer.toHexString(value);
}
}
@@ -312,7 +321,8 @@
| FLAG_PASSWORD_INPUT_TYPE
| FLAG_VIEW_NOT_FOCUSED
| FLAG_SUPPORTS_FILL_DIALOG
- | FLAG_IME_SHOWING);
+ | FLAG_IME_SHOWING
+ | FLAG_RESET_FILL_DIALOG_STATE);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -473,7 +483,8 @@
| FLAG_PASSWORD_INPUT_TYPE
| FLAG_VIEW_NOT_FOCUSED
| FLAG_SUPPORTS_FILL_DIALOG
- | FLAG_IME_SHOWING);
+ | FLAG_IME_SHOWING
+ | FLAG_RESET_FILL_DIALOG_STATE);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -495,10 +506,10 @@
};
@DataClass.Generated(
- time = 1647856966565L,
+ time = 1663290803064L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
new file mode 100644
index 0000000..85f5056
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.MagnificationConfig;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to
+ * interact with the windows in the display that this proxy represents. Proxying the default display
+ * or a display that is not tracked will throw an exception. Only the real user has access to global
+ * clients like SystemUI.
+ *
+ * <p>
+ * To register and unregister a proxy, use
+ * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)}
+ * and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app
+ * that has registered the proxy dies, the system will remove the proxy.
+ *
+ * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods
+ * @hide
+ */
+@SystemApi
+public abstract class AccessibilityDisplayProxy {
+ private static final String LOG_TAG = "AccessibilityDisplayProxy";
+ private static final int INVALID_CONNECTION_ID = -1;
+
+ private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
+ private Executor mExecutor;
+ private int mConnectionId = INVALID_CONNECTION_ID;
+ private int mDisplayId;
+ IAccessibilityServiceClient mServiceClient;
+
+ /**
+ * Constructs an AccessibilityDisplayProxy instance.
+ * @param displayId the id of the display to proxy.
+ * @param executor the executor used to execute proxy callbacks.
+ * @param installedAndEnabledServices the list of infos representing the installed and
+ * enabled a11y services.
+ */
+ public AccessibilityDisplayProxy(int displayId, @NonNull Executor executor,
+ @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
+ mDisplayId = displayId;
+ mExecutor = executor;
+ // Typically, the context is the Service context of an accessibility service.
+ // Context is used for ResolveInfo check, which a proxy won't have, IME input
+ // (FLAG_INPUT_METHOD_EDITOR), which the proxy doesn't need, and tracing
+ // A11yInteractionClient methods.
+ // TODO(254097475): Enable tracing, potentially without exposing Context.
+ mServiceClient = new IAccessibilityServiceClientImpl(null, mExecutor);
+ mInstalledAndEnabledServices = installedAndEnabledServices;
+ }
+
+ /**
+ * Returns the id of the display being proxy-ed.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * An IAccessibilityServiceClient that handles interrupts and accessibility events.
+ */
+ private class IAccessibilityServiceClientImpl extends
+ AccessibilityService.IAccessibilityServiceClientWrapper {
+
+ IAccessibilityServiceClientImpl(Context context, Executor executor) {
+ super(context, executor, new AccessibilityService.Callbacks() {
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ // TODO: call AccessiiblityProxy.onAccessibilityEvent
+ }
+
+ @Override
+ public void onInterrupt() {
+ // TODO: call AccessiiblityProxy.onInterrupt
+ }
+ @Override
+ public void onServiceConnected() {
+ // TODO: send service infos and call AccessiiblityProxy.onProxyConnected
+ }
+ @Override
+ public void init(int connectionId, IBinder windowToken) {
+ mConnectionId = connectionId;
+ }
+
+ @Override
+ public boolean onGesture(AccessibilityGestureEvent gestureInfo) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {
+ }
+
+ @Override
+ public void onTouchStateChanged(int displayId, int state) {
+ }
+
+ @Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ }
+
+ @Override
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
+ }
+
+ @Override
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ }
+
+ @Override
+ public void onAccessibilityButtonClicked(int displayId) {
+ }
+
+ @Override
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ }
+
+ @Override
+ public void onSystemActionsChanged() {
+ }
+
+ @Override
+ public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ }
+
+ @Override
+ public void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, boolean restarting) {
+ }
+ });
+ }
+ }
+}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 5433fa0..423c560 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1921,6 +1921,67 @@
}
}
+ /**
+ * Registers an {@link AccessibilityDisplayProxy}, so this proxy can access UI content specific
+ * to its display.
+ *
+ * @param proxy the {@link AccessibilityDisplayProxy} to register.
+ * @return {@code true} if the proxy is successfully registered.
+ *
+ * @throws IllegalArgumentException if the proxy's display is not currently tracked by a11y, is
+ * {@link android.view.Display#DEFAULT_DISPLAY}, is or lower than
+ * {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed.
+ *
+ * @throws SecurityException if the app does not hold the
+ * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public boolean registerDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+
+ try {
+ return service.registerProxyForDisplay(proxy.mServiceClient, proxy.getDisplayId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters an {@link AccessibilityDisplayProxy}.
+ *
+ * @return {@code true} if the proxy is successfully unregistered.
+ *
+ * @throws SecurityException if the app does not hold the
+ * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public boolean unregisterDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+ try {
+ return service.unregisterProxyForDisplay(proxy.getDisplayId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
private IAccessibilityManager getServiceLocked() {
if (mService == null) {
tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36fdcce4..a251948 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -109,9 +109,9 @@
oneway void setAccessibilityWindowAttributes(int displayId, int windowId, int userId, in AccessibilityWindowAttributes attributes);
- // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
boolean registerProxyForDisplay(IAccessibilityServiceClient proxy, int displayId);
- // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
boolean unregisterProxyForDisplay(int displayId);
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 70cfc3e..ef683b7 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -19,6 +19,7 @@
import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
@@ -734,7 +735,7 @@
* Autofill will automatically trigger a fill request after activity
* start if there is any field is autofillable. But if there is a field that
* triggered autofill, it is unnecessary to trigger again through
- * AutofillManager#notifyViewEnteredForActivityStarted.
+ * AutofillManager#notifyViewEnteredForFillDialog.
*/
private AtomicBoolean mIsFillRequested;
@@ -747,6 +748,10 @@
private final String[] mFillDialogEnabledHints;
+ // Tracked all views that have appeared, including views that there are no
+ // dataset in responses. Used to avoid request pre-fill request again and again.
+ private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>();
+
/** @hide */
public interface AutofillClient {
/**
@@ -1192,6 +1197,16 @@
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
+ synchronized (mLock) {
+ if (mTrackedViews != null) {
+ // To support the fill dialog can show for the autofillable Views in
+ // different pages but in the same Activity. We need to reset the
+ // mIsFillRequested flag to allow asking for a new FillRequest when
+ // user switches to other page
+ mTrackedViews.checkViewState(v.getAutofillId());
+ }
+ }
+
// Skip if the fill request has been performed for a view.
if (mIsFillRequested.get()) {
return;
@@ -1318,6 +1333,10 @@
}
mForAugmentedAutofillOnly = false;
}
+
+ if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) {
+ flags |= FLAG_RESET_FILL_DIALOG_STATE;
+ }
updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
}
addEnteredIdLocked(id);
@@ -2217,6 +2236,7 @@
mIsFillRequested.set(false);
mShowAutofillDialogCalled = false;
mFillDialogTriggerIds = null;
+ mAllTrackedViews.clear();
if (resetEnteredIds) {
mEnteredIds = null;
}
@@ -2776,14 +2796,9 @@
+ ", mFillableIds=" + mFillableIds
+ ", mEnabled=" + mEnabled
+ ", mSessionId=" + mSessionId);
-
}
+
if (mEnabled && mSessionId == sessionId) {
- if (saveOnAllViewsInvisible) {
- mTrackedViews = new TrackedViews(trackedIds);
- } else {
- mTrackedViews = null;
- }
mSaveOnFinish = saveOnFinish;
if (fillableIds != null) {
if (mFillableIds == null) {
@@ -2805,6 +2820,27 @@
mSaveTriggerId = saveTriggerId;
setNotifyOnClickLocked(mSaveTriggerId, true);
}
+
+ if (!saveOnAllViewsInvisible) {
+ trackedIds = null;
+ }
+
+ final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
+ if (mFillableIds != null) {
+ allFillableIds.addAll(mFillableIds);
+ }
+ if (trackedIds != null) {
+ for (AutofillId id : trackedIds) {
+ id.resetSessionId();
+ allFillableIds.add(id);
+ }
+ }
+
+ if (!allFillableIds.isEmpty()) {
+ mTrackedViews = new TrackedViews(trackedIds, Helper.toArray(allFillableIds));
+ } else {
+ mTrackedViews = null;
+ }
}
}
}
@@ -3576,10 +3612,19 @@
*/
private class TrackedViews {
/** Visible tracked views */
- @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
+ @NonNull private final ArraySet<AutofillId> mVisibleTrackedIds;
/** Invisible tracked views */
- @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
+ @NonNull private final ArraySet<AutofillId> mInvisibleTrackedIds;
+
+ /** Visible tracked views for fill dialog */
+ @NonNull private final ArraySet<AutofillId> mVisibleDialogTrackedIds;
+
+ /** Invisible tracked views for fill dialog */
+ @NonNull private final ArraySet<AutofillId> mInvisibleDialogTrackedIds;
+
+ boolean mHasNewTrackedView;
+ boolean mIsTrackedSaveView;
/**
* Check if set is null or value is in set.
@@ -3645,43 +3690,65 @@
*
* @param trackedIds The views to be tracked
*/
- TrackedViews(@Nullable AutofillId[] trackedIds) {
- final AutofillClient client = getClient();
- if (!ArrayUtils.isEmpty(trackedIds) && client != null) {
- final boolean[] isVisible;
+ TrackedViews(@Nullable AutofillId[] trackedIds, @Nullable AutofillId[] allTrackedIds) {
+ mVisibleTrackedIds = new ArraySet<>();
+ mInvisibleTrackedIds = new ArraySet<>();
+ if (!ArrayUtils.isEmpty(trackedIds)) {
+ mIsTrackedSaveView = true;
+ initialTrackedViews(trackedIds, mVisibleTrackedIds, mInvisibleTrackedIds);
+ }
- if (client.autofillClientIsVisibleForAutofill()) {
- if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
- isVisible = client.autofillClientGetViewVisibility(trackedIds);
- } else {
- // All false
- isVisible = new boolean[trackedIds.length];
- }
-
- final int numIds = trackedIds.length;
- for (int i = 0; i < numIds; i++) {
- final AutofillId id = trackedIds[i];
- id.resetSessionId();
-
- if (isVisible[i]) {
- mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
- } else {
- mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
- }
- }
+ mVisibleDialogTrackedIds = new ArraySet<>();
+ mInvisibleDialogTrackedIds = new ArraySet<>();
+ if (!ArrayUtils.isEmpty(allTrackedIds)) {
+ initialTrackedViews(allTrackedIds, mVisibleDialogTrackedIds,
+ mInvisibleDialogTrackedIds);
+ mAllTrackedViews.addAll(Arrays.asList(allTrackedIds));
}
if (sVerbose) {
Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): "
+ " mVisibleTrackedIds=" + mVisibleTrackedIds
- + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
+ + " mInvisibleTrackedIds=" + mInvisibleTrackedIds
+ + " allTrackedIds=" + Arrays.toString(allTrackedIds)
+ + " mVisibleDialogTrackedIds=" + mVisibleDialogTrackedIds
+ + " mInvisibleDialogTrackedIds=" + mInvisibleDialogTrackedIds);
}
- if (mVisibleTrackedIds == null) {
+ if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
}
}
+ private void initialTrackedViews(AutofillId[] trackedIds,
+ @NonNull ArraySet<AutofillId> visibleSet,
+ @NonNull ArraySet<AutofillId> invisibleSet) {
+ final boolean[] isVisible;
+ final AutofillClient client = getClient();
+ if (ArrayUtils.isEmpty(trackedIds) || client == null) {
+ return;
+ }
+ if (client.autofillClientIsVisibleForAutofill()) {
+ if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
+ isVisible = client.autofillClientGetViewVisibility(trackedIds);
+ } else {
+ // All false
+ isVisible = new boolean[trackedIds.length];
+ }
+
+ final int numIds = trackedIds.length;
+ for (int i = 0; i < numIds; i++) {
+ final AutofillId id = trackedIds[i];
+ id.resetSessionId();
+
+ if (isVisible[i]) {
+ addToSet(visibleSet, id);
+ } else {
+ addToSet(invisibleSet, id);
+ }
+ }
+ }
+
/**
* Called when a {@link View view's} visibility changes.
*
@@ -3698,22 +3765,37 @@
if (isClientVisibleForAutofillLocked()) {
if (isVisible) {
if (isInSet(mInvisibleTrackedIds, id)) {
- mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
- mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+ removeFromSet(mInvisibleTrackedIds, id);
+ addToSet(mVisibleTrackedIds, id);
+ }
+ if (isInSet(mInvisibleDialogTrackedIds, id)) {
+ removeFromSet(mInvisibleDialogTrackedIds, id);
+ addToSet(mVisibleDialogTrackedIds, id);
}
} else {
if (isInSet(mVisibleTrackedIds, id)) {
- mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
- mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+ removeFromSet(mVisibleTrackedIds, id);
+ addToSet(mInvisibleTrackedIds, id);
+ }
+ if (isInSet(mVisibleDialogTrackedIds, id)) {
+ removeFromSet(mVisibleDialogTrackedIds, id);
+ addToSet(mInvisibleDialogTrackedIds, id);
}
}
}
- if (mVisibleTrackedIds == null) {
+ if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
if (sVerbose) {
Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds);
}
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
+
+ }
+ if (mVisibleDialogTrackedIds.isEmpty()) {
+ if (sVerbose) {
+ Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds);
+ }
+ processNoVisibleTrackedAllViews();
}
}
@@ -3727,66 +3809,66 @@
// The visibility of the views might have changed while the client was not be visible,
// hence update the visibility state for all views.
AutofillClient client = getClient();
- ArraySet<AutofillId> updatedVisibleTrackedIds = null;
- ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
if (client != null) {
if (sVerbose) {
Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds
+ " vis=" + mVisibleTrackedIds);
}
- if (mInvisibleTrackedIds != null) {
- final ArrayList<AutofillId> orderedInvisibleIds =
- new ArrayList<>(mInvisibleTrackedIds);
- final boolean[] isVisible = client.autofillClientGetViewVisibility(
- Helper.toArray(orderedInvisibleIds));
- final int numInvisibleTrackedIds = orderedInvisibleIds.size();
- for (int i = 0; i < numInvisibleTrackedIds; i++) {
- final AutofillId id = orderedInvisibleIds.get(i);
- if (isVisible[i]) {
- updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
-
- if (sDebug) {
- Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
- }
- } else {
- updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
- }
- }
- }
-
- if (mVisibleTrackedIds != null) {
- final ArrayList<AutofillId> orderedVisibleIds =
- new ArrayList<>(mVisibleTrackedIds);
- final boolean[] isVisible = client.autofillClientGetViewVisibility(
- Helper.toArray(orderedVisibleIds));
-
- final int numVisibleTrackedIds = orderedVisibleIds.size();
- for (int i = 0; i < numVisibleTrackedIds; i++) {
- final AutofillId id = orderedVisibleIds.get(i);
-
- if (isVisible[i]) {
- updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
- } else {
- updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
-
- if (sDebug) {
- Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
- }
- }
- }
- }
-
- mInvisibleTrackedIds = updatedInvisibleTrackedIds;
- mVisibleTrackedIds = updatedVisibleTrackedIds;
+ onVisibleForAutofillChangedInternalLocked(mVisibleTrackedIds, mInvisibleTrackedIds);
+ onVisibleForAutofillChangedInternalLocked(
+ mVisibleDialogTrackedIds, mInvisibleDialogTrackedIds);
}
- if (mVisibleTrackedIds == null) {
+ if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
if (sVerbose) {
- Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
+ Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
}
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
}
+ if (mVisibleDialogTrackedIds.isEmpty()) {
+ if (sVerbose) {
+ Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
+ }
+ processNoVisibleTrackedAllViews();
+ }
+ }
+
+ void onVisibleForAutofillChangedInternalLocked(@NonNull ArraySet<AutofillId> visibleSet,
+ @NonNull ArraySet<AutofillId> invisibleSet) {
+ // The visibility of the views might have changed while the client was not be visible,
+ // hence update the visibility state for all views.
+ if (sVerbose) {
+ Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + invisibleSet
+ + " vis=" + visibleSet);
+ }
+
+ ArraySet<AutofillId> allTrackedIds = new ArraySet<>();
+ allTrackedIds.addAll(visibleSet);
+ allTrackedIds.addAll(invisibleSet);
+ if (!allTrackedIds.isEmpty()) {
+ visibleSet.clear();
+ invisibleSet.clear();
+ initialTrackedViews(Helper.toArray(allTrackedIds), visibleSet, invisibleSet);
+ }
+ }
+
+ private void processNoVisibleTrackedAllViews() {
+ mShowAutofillDialogCalled = false;
+ }
+
+ void checkViewState(AutofillId id) {
+ if (mAllTrackedViews.contains(id)) {
+ return;
+ }
+ // Add the id as tracked to avoid triggering fill request again and again.
+ mAllTrackedViews.add(id);
+ if (mHasNewTrackedView) {
+ return;
+ }
+ // First one new tracks view
+ mIsFillRequested.set(false);
+ mHasNewTrackedView = true;
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 1664637..d067d4b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -378,7 +378,7 @@
private final Object mLock = new Object();
@NonNull
- private final Context mContext;
+ private final StrippedContext mContext;
@NonNull
private final IContentCaptureManager mService;
@@ -414,9 +414,37 @@
}
/** @hide */
+ static class StrippedContext {
+ final String mPackageName;
+ final String mContext;
+ final @UserIdInt int mUserId;
+
+ private StrippedContext(Context context) {
+ mPackageName = context.getPackageName();
+ mContext = context.toString();
+ mUserId = context.getUserId();
+ }
+
+ @Override
+ public String toString() {
+ return mContext;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+ }
+
+ /** @hide */
public ContentCaptureManager(@NonNull Context context,
@NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
- mContext = Objects.requireNonNull(context, "context cannot be null");
+ Objects.requireNonNull(context, "context cannot be null");
+ mContext = new StrippedContext(context);
mService = Objects.requireNonNull(service, "service cannot be null");
mOptions = Objects.requireNonNull(options, "options cannot be null");
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index c32ca9e..a989558 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -36,7 +36,6 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.ComponentName;
-import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -103,7 +102,7 @@
private final AtomicBoolean mDisabled = new AtomicBoolean(false);
@NonNull
- private final Context mContext;
+ private final ContentCaptureManager.StrippedContext mContext;
@NonNull
private final ContentCaptureManager mManager;
@@ -197,7 +196,7 @@
}
}
- protected MainContentCaptureSession(@NonNull Context context,
+ protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
@NonNull ContentCaptureManager manager, @NonNull Handler handler,
@NonNull IContentCaptureManager systemServerInterface) {
mContext = context;
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 7f859d6..1afa987 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -489,6 +489,7 @@
}
@AnyThread
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
static void addVirtualStylusIdForTestSession(IInputMethodClient client) {
final IInputMethodManager service = getService();
if (service == null) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index eb72405..9106ce2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2596,6 +2596,7 @@
* @hide
*/
@TestApi
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
public void addVirtualStylusIdForTestSession() {
synchronized (mH) {
IInputMethodManagerGlobalInvoker.addVirtualStylusIdForTestSession(mClient);
diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java
index 9f03289..0449a16 100644
--- a/core/java/android/view/inputmethod/InsertGesture.java
+++ b/core/java/android/view/inputmethod/InsertGesture.java
@@ -21,7 +21,6 @@
import android.graphics.PointF;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
import android.widget.TextView;
import androidx.annotation.Nullable;
@@ -52,7 +51,8 @@
mPoint = source.readTypedObject(PointF.CREATOR);
}
- /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/
+ /** Returns the text that will be inserted at {@link #getInsertionPoint()}. When text is
+ * empty, cursor should be moved the insertion point. **/
@NonNull
public String getTextToInsert() {
return mTextToInsert;
@@ -75,7 +75,11 @@
private PointF mPoint;
private String mFallbackText;
- /** set the text that will be inserted at {@link #setInsertionPoint(PointF)} **/
+ /**
+ * Set the text that will be inserted at {@link #setInsertionPoint(PointF)}. When set with
+ * an empty string, cursor will be moved to {@link #getInsertionPoint()} and no text
+ * would be inserted.
+ */
@NonNull
@SuppressLint("MissingGetterMatchingBuilder")
public Builder setTextToInsert(@NonNull String text) {
@@ -114,8 +118,8 @@
if (mPoint == null) {
throw new IllegalArgumentException("Insertion point must be set.");
}
- if (TextUtils.isEmpty(mText)) {
- throw new IllegalArgumentException("Text to insert must be non-empty.");
+ if (mText == null) {
+ throw new IllegalArgumentException("Text to insert must be set.");
}
return new InsertGesture(mText, mPoint, mFallbackText);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 423642a..f7bb16e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -147,9 +147,9 @@
boolean isStylusHandwritingAvailableAsUser(int userId);
/** add virtual stylus id for test Stylus handwriting session **/
- @EnforcePermission("INJECT_EVENTS")
+ @EnforcePermission("TEST_INPUT_METHOD")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.INJECT_EVENTS)")
+ + "android.Manifest.permission.TEST_INPUT_METHOD)")
void addVirtualStylusIdForTestSession(in IInputMethodClient client);
/** Set a stylus idle-timeout after which handwriting {@code InkWindow} will be removed. */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d8ecb5c..16e0a59 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1150,6 +1150,18 @@
android:description="@string/permdesc_readMediaImages"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to read image or video files from external storage that a user has
+ selected via the permission prompt photo picker. Apps can check this permission to verify that
+ a user has decided to use the photo picker, instead of granting access to
+ {@link #READ_MEDIA_IMAGES or #READ_MEDIA_VIDEO}. It does not prevent apps from accessing the
+ standard photo picker manually.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readVisualUserSelect"
+ android:description="@string/permdesc_readVisualUserSelect"
+ android:protectionLevel="dangerous" />
+
<!-- Allows an application to write to external storage.
<p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or
higher, this permission has no effect.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 509de33..1f459c6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1933,6 +1933,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
<string name="permdesc_readMediaImages">Allows the app to read image files from your shared storage.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permlab_readVisualUserSelect">read user selected image and video files from shared storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+ <string name="permdesc_readVisualUserSelect">Allows the app to read image and video files that you select from your shared storage.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
<string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
index 57b9cb1..5bd018b 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -23,11 +23,13 @@
import android.annotation.Nullable;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.os.Parcel;
import org.junit.Test;
public final class ProgramSelectorTest {
+ private static final int CREATOR_ARRAY_SIZE = 2;
private static final int FM_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_FM;
private static final int DAB_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_DAB;
private static final long FM_FREQUENCY = 88500;
@@ -97,6 +99,33 @@
}
@Test
+ public void describeContents_forIdentifier() {
+ assertWithMessage("FM identifier contents")
+ .that(FM_IDENTIFIER.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forIdentifierCreator() {
+ ProgramSelector.Identifier[] identifiers =
+ ProgramSelector.Identifier.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Identifiers").that(identifiers).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void writeToParcel_forIdentifier() {
+ Parcel parcel = Parcel.obtain();
+
+ FM_IDENTIFIER.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ ProgramSelector.Identifier identifierFromParcel =
+ ProgramSelector.Identifier.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Identifier created from parcel")
+ .that(identifierFromParcel).isEqualTo(FM_IDENTIFIER);
+ }
+
+ @Test
public void getProgramType() {
ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
@@ -394,6 +423,34 @@
.that(selector1.strictEquals(selector2)).isTrue();
}
+ @Test
+ public void describeContents_forProgramSelector() {
+ assertWithMessage("FM selector contents")
+ .that(getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null)
+ .describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forProgramSelectorCreator() {
+ ProgramSelector[] programSelectors = ProgramSelector.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Program selectors").that(programSelectors).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void writeToParcel_forProgramSelector() {
+ ProgramSelector selectorExpected =
+ getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+ Parcel parcel = Parcel.obtain();
+
+ selectorExpected.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ ProgramSelector selectorFromParcel = ProgramSelector.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Program selector created from parcel")
+ .that(selectorFromParcel).isEqualTo(selectorExpected);
+ }
+
private ProgramSelector getFmSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
@Nullable long[] vendorIds) {
return new ProgramSelector(FM_PROGRAM_TYPE, FM_IDENTIFIER, secondaryIds, vendorIds);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
index 42143b9..6e1bb4b4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
@@ -22,6 +22,7 @@
import android.hardware.radio.Announcement;
import android.hardware.radio.ProgramSelector;
+import android.os.Parcel;
import android.util.ArrayMap;
import org.junit.Test;
@@ -83,4 +84,35 @@
vendorInfo.put("vendorKeyMock", "vendorValueMock");
return vendorInfo;
}
+
+ @Test
+ public void describeContents_forAnnouncement() {
+ assertWithMessage("Radio announcement contents")
+ .that(TEST_ANNOUNCEMENT.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forAnnouncementCreator() {
+ int sizeExpected = 2;
+
+ Announcement[] announcements = Announcement.CREATOR.newArray(sizeExpected);
+
+ assertWithMessage("Announcements").that(announcements).hasLength(sizeExpected);
+ }
+
+ @Test
+ public void writeToParcel_forAnnouncement() {
+ Parcel parcel = Parcel.obtain();
+
+ TEST_ANNOUNCEMENT.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ Announcement announcementFromParcel = Announcement.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Selector of announcement created from parcel")
+ .that(announcementFromParcel.getSelector()).isEqualTo(FM_PROGRAM_SELECTOR);
+ assertWithMessage("Type of announcement created from parcel")
+ .that(announcementFromParcel.getType()).isEqualTo(TRAFFIC_ANNOUNCEMENT_TYPE);
+ assertWithMessage("Vendor info of announcement created from parcel")
+ .that(announcementFromParcel.getVendorInfo()).isEqualTo(VENDOR_INFO);
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index be4d0d4..f838a5d 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -33,6 +33,7 @@
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -80,6 +81,8 @@
private static final int[] SUPPORTED_IDENTIFIERS_TYPES = new int[]{
ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI};
+ private static final int CREATOR_ARRAY_SIZE = 3;
+
private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
createFmBandDescriptor();
private static final RadioManager.AmBandDescriptor AM_BAND_DESCRIPTOR =
@@ -173,6 +176,22 @@
}
@Test
+ public void describeContents_forBandDescriptor() {
+ RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor();
+
+ assertWithMessage("Band Descriptor contents")
+ .that(bandDescriptor.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forBandDescriptorCreator() {
+ RadioManager.BandDescriptor[] bandDescriptors =
+ RadioManager.BandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Band Descriptors").that(bandDescriptors).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void isAmBand_forAmBandDescriptor_returnsTrue() {
RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -219,18 +238,73 @@
}
@Test
+ public void describeContents_forFmBandDescriptor() {
+ assertWithMessage("FM Band Descriptor contents")
+ .that(FM_BAND_DESCRIPTOR.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forFmBandDescriptor() {
+ Parcel parcel = Parcel.obtain();
+
+ FM_BAND_DESCRIPTOR.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.FmBandDescriptor fmBandDescriptorFromParcel =
+ RadioManager.FmBandDescriptor.CREATOR.createFromParcel(parcel);
+ assertWithMessage("FM Band Descriptor created from parcel")
+ .that(fmBandDescriptorFromParcel).isEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void newArray_forFmBandDescriptorCreator() {
+ RadioManager.FmBandDescriptor[] fmBandDescriptors =
+ RadioManager.FmBandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("FM Band Descriptors")
+ .that(fmBandDescriptors).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void isStereoSupported_forAmBandDescriptor() {
assertWithMessage("AM Band Descriptor stereo")
.that(AM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED);
}
@Test
+ public void describeContents_forAmBandDescriptor() {
+ assertWithMessage("AM Band Descriptor contents")
+ .that(AM_BAND_DESCRIPTOR.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forAmBandDescriptor() {
+ Parcel parcel = Parcel.obtain();
+
+ AM_BAND_DESCRIPTOR.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.AmBandDescriptor amBandDescriptorFromParcel =
+ RadioManager.AmBandDescriptor.CREATOR.createFromParcel(parcel);
+ assertWithMessage("FM Band Descriptor created from parcel")
+ .that(amBandDescriptorFromParcel).isEqualTo(AM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void newArray_forAmBandDescriptorCreator() {
+ RadioManager.AmBandDescriptor[] amBandDescriptors =
+ RadioManager.AmBandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("AM Band Descriptors")
+ .that(amBandDescriptors).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void equals_withSameFmBandDescriptors_returnsTrue() {
- RadioManager.FmBandDescriptor fmBandDescriptor1 = createFmBandDescriptor();
- RadioManager.FmBandDescriptor fmBandDescriptor2 = createFmBandDescriptor();
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = createFmBandDescriptor();
assertWithMessage("The same FM Band Descriptor")
- .that(fmBandDescriptor1).isEqualTo(fmBandDescriptor2);
+ .that(FM_BAND_DESCRIPTOR).isEqualTo(fmBandDescriptorCompared);
}
@Test
@@ -258,6 +332,44 @@
}
@Test
+ public void hashCode_withSameFmBandDescriptors_equals() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = createFmBandDescriptor();
+
+ assertWithMessage("Hash code of the same FM Band Descriptor")
+ .that(fmBandDescriptorCompared.hashCode()).isEqualTo(FM_BAND_DESCRIPTOR.hashCode());
+ }
+
+ @Test
+ public void hashCode_withSameAmBandDescriptors_equals() {
+ RadioManager.AmBandDescriptor amBandDescriptorCompared = createAmBandDescriptor();
+
+ assertWithMessage("Hash code of the same AM Band Descriptor")
+ .that(amBandDescriptorCompared.hashCode()).isEqualTo(AM_BAND_DESCRIPTOR.hashCode());
+ }
+
+ @Test
+ public void hashCode_withFmBandDescriptorsOfDifferentAfSupports_notEquals() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("Hash code of FM Band Descriptor of different spacing")
+ .that(fmBandDescriptorCompared.hashCode())
+ .isNotEqualTo(FM_BAND_DESCRIPTOR.hashCode());
+ }
+
+ @Test
+ public void hashCode_withAmBandDescriptorsOfDifferentSpacings_notEquals() {
+ RadioManager.AmBandDescriptor amBandDescriptorCompared =
+ new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING * 2, STEREO_SUPPORTED);
+
+ assertWithMessage("Hash code of AM Band Descriptor of different spacing")
+ .that(amBandDescriptorCompared.hashCode())
+ .isNotEqualTo(AM_BAND_DESCRIPTOR.hashCode());
+ }
+
+ @Test
public void getType_forBandConfig() {
RadioManager.BandConfig fmBandConfig = createFmBandConfig();
@@ -298,8 +410,24 @@
}
@Test
+ public void describeContents_forBandConfig() {
+ RadioManager.BandConfig bandConfig = createFmBandConfig();
+
+ assertWithMessage("FM Band Config contents")
+ .that(bandConfig.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forBandConfigCreator() {
+ RadioManager.BandConfig[] bandConfigs =
+ RadioManager.BandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Band Configs").that(bandConfigs).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void getStereo_forFmBandConfig() {
- assertWithMessage("FM Band Config stereo ")
+ assertWithMessage("FM Band Config stereo")
.that(FM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED);
}
@@ -328,12 +456,66 @@
}
@Test
+ public void describeContents_forFmBandConfig() {
+ assertWithMessage("FM Band Config contents")
+ .that(FM_BAND_CONFIG.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forFmBandConfig() {
+ Parcel parcel = Parcel.obtain();
+
+ FM_BAND_CONFIG.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.FmBandConfig fmBandConfigFromParcel =
+ RadioManager.FmBandConfig.CREATOR.createFromParcel(parcel);
+ assertWithMessage("FM Band Config created from parcel")
+ .that(fmBandConfigFromParcel).isEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void newArray_forFmBandConfigCreator() {
+ RadioManager.FmBandConfig[] fmBandConfigs =
+ RadioManager.FmBandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("FM Band Configs").that(fmBandConfigs).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void getStereo_forAmBandConfig() {
assertWithMessage("AM Band Config stereo")
.that(AM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED);
}
@Test
+ public void describeContents_forAmBandConfig() {
+ assertWithMessage("AM Band Config contents")
+ .that(AM_BAND_CONFIG.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forAmBandConfig() {
+ Parcel parcel = Parcel.obtain();
+
+ AM_BAND_CONFIG.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.AmBandConfig amBandConfigFromParcel =
+ RadioManager.AmBandConfig.CREATOR.createFromParcel(parcel);
+ assertWithMessage("AM Band Config created from parcel")
+ .that(amBandConfigFromParcel).isEqualTo(AM_BAND_CONFIG);
+ }
+
+ @Test
+ public void newArray_forAmBandConfigCreator() {
+ RadioManager.AmBandConfig[] amBandConfigs =
+ RadioManager.AmBandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("AM Band Configs").that(amBandConfigs).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void equals_withSameFmBandConfigs_returnsTrue() {
RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig();
@@ -387,6 +569,43 @@
}
@Test
+ public void hashCode_withSameFmBandConfigs_equals() {
+ RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig();
+
+ assertWithMessage("Hash code of the same FM Band Config")
+ .that(FM_BAND_CONFIG.hashCode()).isEqualTo(fmBandConfigCompared.hashCode());
+ }
+
+ @Test
+ public void hashCode_withSameAmBandConfigs_equals() {
+ RadioManager.AmBandConfig amBandConfigCompared = createAmBandConfig();
+
+ assertWithMessage("Hash code of the same AM Band Config")
+ .that(amBandConfigCompared.hashCode()).isEqualTo(AM_BAND_CONFIG.hashCode());
+ }
+
+ @Test
+ public void hashCode_withFmBandConfigsOfDifferentTypes_notEquals() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM_HD, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("Hash code of FM Band Config with different type")
+ .that(fmBandConfigCompared.hashCode()).isNotEqualTo(FM_BAND_CONFIG.hashCode());
+ }
+
+ @Test
+ public void hashCode_withAmBandConfigsOfDifferentStereoSupports_notEquals() {
+ RadioManager.AmBandConfig amBandConfigCompared = new RadioManager.AmBandConfig(
+ new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED));
+
+ assertWithMessage("Hash code of AM Band Config with different stereo support")
+ .that(amBandConfigCompared.hashCode()).isNotEqualTo(AM_BAND_CONFIG.hashCode());
+ }
+
+ @Test
public void getId_forModuleProperties() {
assertWithMessage("Properties id")
.that(AMFM_PROPERTIES.getId()).isEqualTo(PROPERTIES_ID);
@@ -509,6 +728,12 @@
}
@Test
+ public void describeContents_forModuleProperties() {
+ assertWithMessage("Module properties contents")
+ .that(AMFM_PROPERTIES.describeContents()).isEqualTo(0);
+ }
+
+ @Test
public void equals_withSameProperties_returnsTrue() {
RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
@@ -530,6 +755,23 @@
}
@Test
+ public void hashCode_withSameModuleProperties_equals() {
+ RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+
+ assertWithMessage("Hash code of the same module properties")
+ .that(propertiesCompared.hashCode()).isEqualTo(AMFM_PROPERTIES.hashCode());
+ }
+
+ @Test
+ public void newArray_forModulePropertiesCreator() {
+ RadioManager.ModuleProperties[] modulePropertiesArray =
+ RadioManager.ModuleProperties.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Module properties array")
+ .that(modulePropertiesArray).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
public void getSelector_forProgramInfo() {
assertWithMessage("Selector of DAB program info")
.that(DAB_PROGRAM_INFO.getSelector()).isEqualTo(DAB_SELECTOR);
@@ -549,7 +791,7 @@
@Test
public void getRelatedContent_forProgramInfo() {
- assertWithMessage("Related contents of DAB program info")
+ assertWithMessage("DAB program info contents")
.that(DAB_PROGRAM_INFO.getRelatedContent())
.containsExactly(DAB_SID_EXT_IDENTIFIER_RELATED);
}
@@ -627,6 +869,33 @@
}
@Test
+ public void describeContents_forProgramInfo() {
+ assertWithMessage("Program info contents")
+ .that(DAB_PROGRAM_INFO.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forProgramInfoCreator() {
+ RadioManager.ProgramInfo[] programInfoArray =
+ RadioManager.ProgramInfo.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Program infos").that(programInfoArray).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void writeToParcel_forProgramInfo() {
+ Parcel parcel = Parcel.obtain();
+
+ DAB_PROGRAM_INFO.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.ProgramInfo programInfoFromParcel =
+ RadioManager.ProgramInfo.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Program info created from parcel")
+ .that(programInfoFromParcel).isEqualTo(DAB_PROGRAM_INFO);
+ }
+
+ @Test
public void equals_withSameProgramInfo_returnsTrue() {
RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(DAB_SELECTOR);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
index fe15597..5771135 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
@@ -20,18 +20,63 @@
import static org.junit.Assert.assertThrows;
+import android.graphics.Bitmap;
import android.hardware.radio.RadioMetadata;
+import android.os.Parcel;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.Set;
+@RunWith(MockitoJUnitRunner.class)
public final class RadioMetadataTest {
+ private static final int CREATOR_ARRAY_SIZE = 3;
private static final int INT_KEY_VALUE = 1;
+ private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200;
+ private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1;
private final RadioMetadata.Builder mBuilder = new RadioMetadata.Builder();
+ @Mock
+ private Bitmap mBitmapValue;
+
+ @Test
+ public void describeContents_forClock() {
+ RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
+ TEST_TIME_ZONE_OFFSET_MINUTES);
+
+ assertWithMessage("Describe contents for metadata clock")
+ .that(clock.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forClockCreator() {
+ RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void writeToParcel_forClock() {
+ RadioMetadata.Clock clockExpected = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
+ TEST_TIME_ZONE_OFFSET_MINUTES);
+ Parcel parcel = Parcel.obtain();
+
+ clockExpected.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel);
+ assertWithMessage("UTC second since epoch of metadata clock created from parcel")
+ .that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
+ assertWithMessage("Time zone offset minutes of metadata clock created from parcel")
+ .that(clockFromParcel.getTimezoneOffsetMinutes())
+ .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
+ }
+
@Test
public void putString_withIllegalKey() {
String invalidStringKey = RadioMetadata.METADATA_KEY_RDS_PI;
@@ -129,22 +174,56 @@
}
@Test
+ public void getBitmap_withKeyInMetadata() {
+ String key = RadioMetadata.METADATA_KEY_ICON;
+ RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build();
+
+ assertWithMessage("Bitmap value for key %s in metadata", key)
+ .that(metadata.getBitmap(key)).isEqualTo(mBitmapValue);
+ }
+
+ @Test
+ public void getBitmap_withKeyNotInMetadata() {
+ String key = RadioMetadata.METADATA_KEY_ICON;
+ RadioMetadata metadata = mBuilder.build();
+
+ assertWithMessage("Bitmap value for key %s not in metadata", key)
+ .that(metadata.getBitmap(key)).isNull();
+ }
+
+ @Test
+ public void getBitmapId_withKeyInMetadata() {
+ String key = RadioMetadata.METADATA_KEY_ART;
+ RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
+
+ assertWithMessage("Bitmap id value for key %s in metadata", key)
+ .that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE);
+ }
+
+ @Test
+ public void getBitmapId_withKeyNotInMetadata() {
+ String key = RadioMetadata.METADATA_KEY_ART;
+ RadioMetadata metadata = mBuilder.build();
+
+ assertWithMessage("Bitmap id value for key %s not in metadata", key)
+ .that(metadata.getBitmapId(key)).isEqualTo(0);
+ }
+
+ @Test
public void getClock_withKeyInMetadata() {
String key = RadioMetadata.METADATA_KEY_CLOCK;
- long utcSecondsSinceEpochExpected = 200;
- int timezoneOffsetMinutesExpected = 1;
RadioMetadata metadata = mBuilder
- .putClock(key, utcSecondsSinceEpochExpected, timezoneOffsetMinutesExpected)
+ .putClock(key, TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES)
.build();
RadioMetadata.Clock clockExpected = metadata.getClock(key);
assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key)
.that(clockExpected.getUtcEpochSeconds())
- .isEqualTo(utcSecondsSinceEpochExpected);
+ .isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key)
.that(clockExpected.getTimezoneOffsetMinutes())
- .isEqualTo(timezoneOffsetMinutesExpected);
+ .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
}
@Test
@@ -180,12 +259,13 @@
RadioMetadata metadata = mBuilder
.putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+ .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue)
.build();
Set<String> metadataSet = metadata.keySet();
assertWithMessage("Metadata set of non-empty metadata")
- .that(metadataSet).containsExactly(
+ .that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON,
RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST);
}
@@ -208,4 +288,46 @@
.that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI);
}
+ @Test
+ public void equals_forMetadataWithSameContents_returnsTrue() {
+ RadioMetadata metadata = mBuilder
+ .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
+ .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+ .build();
+ RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
+ RadioMetadata metadataCopied = copyBuilder.build();
+
+ assertWithMessage("Metadata with the same contents")
+ .that(metadataCopied).isEqualTo(metadata);
+ }
+
+ @Test
+ public void describeContents_forMetadata() {
+ RadioMetadata metadata = mBuilder.build();
+
+ assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forRadioMetadataCreator() {
+ RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void writeToParcel_forRadioMetadata() {
+ RadioMetadata metadataExpected = mBuilder
+ .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
+ .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+ .build();
+ Parcel parcel = Parcel.obtain();
+
+ metadataExpected.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Radio metadata created from parcel")
+ .that(metadataFromParcel).isEqualTo(metadataExpected);
+ }
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index bb1a3b18..ee1e10f 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -34,6 +35,7 @@
import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -51,6 +53,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executors;
/**
* Tests for the AccessibilityManager by mocking the backing service.
@@ -70,6 +73,7 @@
LABEL,
DESCRIPTION,
TEST_PENDING_INTENT);
+ private static final int DISPLAY_ID = 22;
@Mock private IAccessibilityManager mMockService;
private MessageCapturingHandler mHandler;
@@ -224,4 +228,45 @@
assertEquals(mFocusColorDefaultValue,
manager.getAccessibilityFocusColor());
}
+
+ @Test
+ public void testRegisterAccessibilityProxy() throws Exception {
+ // Accessibility does not need to be enabled for a proxy to be registered.
+ AccessibilityManager manager =
+ new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService,
+ UserHandle.USER_CURRENT, true);
+
+
+ ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>();
+ infos.add(new AccessibilityServiceInfo());
+ AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos);
+ manager.registerDisplayProxy(proxy);
+ // Cannot access proxy.mServiceClient directly due to visibility.
+ verify(mMockService).registerProxyForDisplay(any(IAccessibilityServiceClient.class),
+ any(Integer.class));
+ }
+
+ @Test
+ public void testUnregisterAccessibilityProxy() throws Exception {
+ // Accessibility does not need to be enabled for a proxy to be registered.
+ final AccessibilityManager manager =
+ new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService,
+ UserHandle.USER_CURRENT, true);
+
+ final ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>();
+ infos.add(new AccessibilityServiceInfo());
+
+ final AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos);
+ manager.registerDisplayProxy(proxy);
+ manager.unregisterDisplayProxy(proxy);
+ verify(mMockService).unregisterProxyForDisplay(proxy.getDisplayId());
+ }
+
+ private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
+ // TODO(241429275): Will override A11yProxy methods in the future.
+ MyAccessibilityProxy(int displayId,
+ @NonNull List<AccessibilityServiceInfo> serviceInfos) {
+ super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 7997892..651d935 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -21,6 +21,7 @@
import com.android.server.wm.traces.common.ComponentNameMatcher
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 9e76575..2bce8e45 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -53,7 +53,7 @@
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp) }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
transitions {
SplitScreenUtils.copyContentInSplit(
instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 45eae2e..4757498 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -55,7 +55,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
}
transitions {
if (tapl.isTablet) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 6cfbb47..1d61955 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -52,7 +52,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
}
transitions {
tapl.goHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index a80c88a..8d771fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -56,7 +56,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
}
transitions {
SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 936afa9..fb7b8b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -34,7 +34,6 @@
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,7 +54,6 @@
get() = {
super.transition(this)
setup {
- tapl.workspace.switchToOverview().dismissAllTasks()
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
@@ -65,7 +63,7 @@
.waitForAndVerify()
}
transitions {
- SplitScreenUtils.splitFromOverview(tapl)
+ SplitScreenUtils.splitFromOverview(tapl, device)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index e6d6379..c841333 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -34,6 +34,7 @@
tapl.setEnableRotation(true)
setRotation(testSpec.startRotation)
tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.workspace.switchToOverview().dismissAllTasks()
}
teardown {
primaryApp.exit(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 6453ed8..ead451f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -25,6 +25,7 @@
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -38,13 +39,16 @@
import com.android.server.wm.traces.common.IComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import java.util.Collections
internal object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
private const val DIVIDER_BAR = "docked_divider_handle"
+ private const val OVERVIEW_SNAPSHOT = "snapshot"
private const val GESTURE_STEP_MS = 16L
private const val LONG_PRESS_TIME_MS = 100L
private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
@@ -55,6 +59,8 @@
get() = By.text("Flicker Test Notification")
private val dividerBarSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+ private val overviewSnapshotSelector: BySelector
+ get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
SimpleAppHelper(
@@ -94,24 +100,39 @@
fun enterSplit(
wmHelper: WindowManagerStateHelper,
tapl: LauncherInstrumentation,
+ device: UiDevice,
primaryApp: StandardAppHelper,
secondaryApp: StandardAppHelper
) {
- tapl.workspace.switchToOverview().dismissAllTasks()
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- splitFromOverview(tapl)
+ splitFromOverview(tapl, device)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
- fun splitFromOverview(tapl: LauncherInstrumentation) {
+ fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
// split to left side.
if (tapl.isTablet) {
- tapl.workspace.switchToOverview().overviewActions.clickSplit().currentTask.open()
+ // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+ // contains more than 3 task views. We need to use uiautomator directly to find the
+ // second task to split.
+ tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+ if (snapshots == null || snapshots.size < 1) {
+ error("Fail to find a overview snapshot to split.")
+ }
+
+ // Find the second task in the upper right corner in split select mode by sorting
+ // 'left' in descending order and 'top' in ascending order.
+ Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
+ t2.getVisibleBounds().left - t1.getVisibleBounds().left})
+ Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top})
+ snapshots[0].click()
} else {
tapl.workspace
.switchToOverview()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index ad7a531..f7610c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -56,7 +56,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
}
transitions {
SplitScreenUtils.doubleTapDividerToSwitch(device)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 553840c..993dba2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -52,7 +52,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 1f117d0..2a552cd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -51,7 +51,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index d7b3ec2..7f81bae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -51,7 +51,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
new file mode 100644
index 0000000..d84954d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch between two split pairs.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+ private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+ private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ teardown {
+ thirdApp.exit(wmHelper)
+ fourthApp.exit(wmHelper)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun cujCompleted() {
+ testSpec.appWindowIsVisibleAtStart(thirdApp)
+ testSpec.appWindowIsVisibleAtStart(fourthApp)
+ testSpec.splitScreenDividerIsVisibleAtStart()
+
+ testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ testSpec.appWindowIsInvisibleAtEnd(thirdApp)
+ testSpec.appWindowIsInvisibleAtEnd(fourthApp)
+ testSpec.splitScreenDividerIsVisibleAtEnd()
+ }
+
+ @Postsubmit
+ @Test
+ fun splitScreenDividerInvisibleAtMiddle() =
+ testSpec.assertLayers {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
+ .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun thirdAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(thirdApp)
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun fourthAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(fourthApp)
+
+ @Postsubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Postsubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Postsubmit
+ @Test
+ fun thirdAppBoundsIsVisibleAtBegin() =
+ testSpec.assertLayersStart {
+ this.splitAppLayerBoundsSnapToDivider(
+ thirdApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false,
+ testSpec.startRotation
+ )
+ }
+
+ @Postsubmit
+ @Test
+ fun fourthAppBoundsIsVisibleAtBegin() =
+ testSpec.assertLayersStart {
+ this.splitAppLayerBoundsSnapToDivider(
+ fourthApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true,
+ testSpec.startRotation
+ )
+ }
+
+ @Postsubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+ @Postsubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ @Postsubmit
+ @Test
+ fun thirdAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(thirdApp)
+
+ @Postsubmit
+ @Test
+ fun fourthAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(fourthApp)
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 251268711)
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@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 taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ }
+ }
+}
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 6990ad0..62ffe38 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -13,4 +13,9 @@
libs: ["SliceStore"],
platform_apis: true,
certificate: "platform",
+ optimize: {
+ proguard_flags_files: [
+ "proguard.flags",
+ ],
+ },
}
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.html b/packages/CarrierDefaultApp/assets/slice_store_test.html
new file mode 100644
index 0000000..7ddbd2d
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.html
@@ -0,0 +1,78 @@
+<!--
+ ~ 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.
+ -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="description" content="
+ This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
+ SliceStoreWebInterface. Test SliceStore APIs using ADB shell commands and the APIs below:
+
+ FROM TERMINAL:
+ Allow device to override carrier configs:
+ $ adb root
+ Set PREMIUM_CAPABILITY_PRIORITIZE_LATENCY enabled:
+ $ adb shell cmd phone cc set-value -p supported_premium_capabilities_int_array 34
+ Set the carrier purchase URL to this test HTML file:
+ $ adb shell cmd phone cc set-value -p premium_capability_purchase_url_string \
+ file:///android_asset/slice_store_test.html
+ OPTIONAL: Allow premium capability purchase on LTE:
+ $ adb shell cmd phone cc set-value -p premium_capability_supported_on_lte_bool true
+ OPTIONAL: Override ServiceState to fake a NR SA connection:
+ $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 20
+
+ FROM TEST ACTIVITY:
+ TelephonyManager tm = getApplicationContext().getSystemService(TelephonyManager.class)
+ tm.isPremiumCapabilityAvailable(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ LinkedBlockingQueue<Integer> purchaseRequests = new LinkedBlockingQueue<>();
+ tm.purchasePremiumCapability(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY,
+ this.getMainExecutor(), request::offer);
+
+ When the test application starts, this HTML will be loaded into the WebView along with the
+ associated JavaScript functions in file:///android_asset/slice_store_test.js.
+ Click on the buttons in the HTML to call the corresponding @JavascriptInterface APIs.
+
+ RESET DEVICE STATE:
+ Clear carrier configurations that were set:
+ $ adb shell cmd phone cc clear-values
+ Clear ServiceState override that was set:
+ $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
+ ">
+ <title>Test SliceStoreActivity</title>
+ <script type="text/javascript" src="slice_store_test.js"></script>
+</head>
+<body>
+ <h1>Test SliceStoreActivity</h1>
+ <h2>Get requested premium capability</h2>
+ <button type="button" onclick="testGetRequestedCapability()">
+ Get requested premium capability
+ </button>
+ <p id="requested_capability"></p>
+
+ <h2>Notify purchase successful</h2>
+ <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
+ Notify purchase successful for 1 minute
+ </button>
+ <p id="purchase_successful"></p>
+
+ <h2>Notify purchase failed</h2>
+ <button type="button" onclick="testNotifyPurchaseFailed()">
+ Notify purchase failed
+ </button>
+ <p id="purchase_failed"></p>
+</body>
+</html>
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.js b/packages/CarrierDefaultApp/assets/slice_store_test.js
new file mode 100644
index 0000000..f12a6da
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+function testGetRequestedCapability() {
+ let capability = SliceStoreWebInterface.getRequestedCapability();
+ document.getElementById("requested_capability").innerHTML =
+ "Premium capability requested: " + capability;
+}
+
+function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
+ SliceStoreWebInterface.notifyPurchaseSuccessful(duration);
+ document.getElementById("purchase_successful").innerHTML =
+ "Notified purchase success for duration: " + duration;
+}
+
+function testNotifyPurchaseFailed() {
+ SliceStoreWebInterface.notifyPurchaseFailed();
+ document.getElementById("purchase_failed").innerHTML =
+ "Notified purchase failed.";
+}
diff --git a/packages/CarrierDefaultApp/proguard.flags b/packages/CarrierDefaultApp/proguard.flags
new file mode 100644
index 0000000..64fec2c
--- /dev/null
+++ b/packages/CarrierDefaultApp/proguard.flags
@@ -0,0 +1,4 @@
+# Keep classes and methods that have the @JavascriptInterface annotation
+-keepclassmembers class * {
+ @android.webkit.JavascriptInterface <methods>;
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
index 602e31c..348e389 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -20,47 +20,63 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.NotificationManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.view.KeyEvent;
import android.webkit.WebView;
import com.android.phone.slicestore.SliceStore;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.concurrent.TimeUnit;
/**
* Activity that launches when the user clicks on the network boost notification.
+ * This will open a {@link WebView} for the carrier website to allow the user to complete the
+ * premium capability purchase.
+ * The carrier website can get the requested premium capability using the JavaScript interface
+ * method {@code SliceStoreWebInterface.getRequestedCapability()}.
+ * If the purchase is successful, the carrier website shall notify SliceStore using the JavaScript
+ * interface method {@code SliceStoreWebInterface.notifyPurchaseSuccessful(duration)}, where
+ * {@code duration} is the duration of the network boost.
+ * If the purchase was not successful, the carrier website shall notify SliceStore using the
+ * JavaScript interface method {@code SliceStoreWebInterface.notifyPurchaseFailed()}.
+ * If either of these notification methods are not called, the purchase cannot be completed
+ * successfully and the purchase request will eventually time out.
*/
public class SliceStoreActivity extends Activity {
private static final String TAG = "SliceStoreActivity";
- private URL mUrl;
- private WebView mWebView;
- private int mPhoneId;
+ private @NonNull WebView mWebView;
+ private @NonNull Context mApplicationContext;
private int mSubId;
- private @TelephonyManager.PremiumCapability int mCapability;
+ @TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
- mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
- SubscriptionManager.INVALID_PHONE_INDEX);
mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
SliceStore.PREMIUM_CAPABILITY_INVALID);
- mUrl = getUrl();
- logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability="
+ mApplicationContext = getApplicationContext();
+ URL url = getUrl();
+ logd("onCreate: subId=" + mSubId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", mUrl=" + mUrl);
- getApplicationContext().getSystemService(NotificationManager.class)
+ + ", url=" + url);
+
+ // Cancel network boost notification
+ mApplicationContext.getSystemService(NotificationManager.class)
.cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+
+ // Verify intent and values are valid
if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
SliceStoreBroadcastReceiver.sendSliceStoreResponse(
@@ -68,10 +84,15 @@
finishAndRemoveTask();
return;
}
- if (mUrl == null) {
- loge("Unable to create a URL from carrier configs.");
- SliceStoreBroadcastReceiver.sendSliceStoreResponse(
- intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR);
+ if (url == null) {
+ String error = "Unable to create a URL from carrier configs.";
+ loge(error);
+ Intent data = new Intent();
+ data.putExtra(SliceStore.EXTRA_FAILURE_CODE,
+ SliceStore.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
+ data.putExtra(SliceStore.EXTRA_FAILURE_REASON, error);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+ mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
return;
}
@@ -83,12 +104,53 @@
return;
}
+ // Create a reference to this activity in SliceStoreBroadcastReceiver
SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
+ // Create and configure WebView
mWebView = new WebView(this);
+ // Enable JavaScript for the carrier purchase website to send results back to SliceStore
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.addJavascriptInterface(new SliceStoreWebInterface(this), "SliceStoreWebInterface");
+
+ // Display WebView
setContentView(mWebView);
- mWebView.loadUrl(mUrl.toString());
- // TODO(b/245882601): Get back response from WebView
+ mWebView.loadUrl(url.toString());
+ }
+
+ protected void onPurchaseSuccessful(long duration) {
+ logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
+ + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes.");
+ Intent intent = new Intent();
+ intent.putExtra(SliceStore.EXTRA_PURCHASE_DURATION, duration);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+ mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_SUCCESS, intent);
+ finishAndRemoveTask();
+ }
+
+ protected void onPurchaseFailed(@SliceStore.FailureCode int failureCode,
+ @Nullable String failureReason) {
+ logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
+ + SliceStore.convertFailureCodeToString(failureCode) + " and reason: "
+ + failureReason);
+ Intent data = new Intent();
+ data.putExtra(SliceStore.EXTRA_FAILURE_CODE, failureCode);
+ data.putExtra(SliceStore.EXTRA_FAILURE_REASON, failureReason);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+ mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
+ finishAndRemoveTask();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ // Pressing back in the WebView will go to the previous page instead of closing SliceStore.
+ if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+ mWebView.goBack();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
}
@Override
@@ -100,8 +162,8 @@
super.onDestroy();
}
- private @Nullable URL getUrl() {
- String url = getApplicationContext().getSystemService(CarrierConfigManager.class)
+ @Nullable private URL getUrl() {
+ String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mSubId).getString(
CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
try {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
index 7eb851d..7867ef1 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import android.telephony.AnomalyReporter;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -37,6 +38,7 @@
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
+import java.util.UUID;
/**
* The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
@@ -47,6 +49,12 @@
public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
private static final String TAG = "SliceStoreBroadcastReceiver";
+ /**
+ * UUID to report an anomaly when receiving a PendingIntent from an application or process
+ * other than the Phone process.
+ */
+ private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
+
/** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
new HashMap<>();
@@ -102,6 +110,28 @@
}
/**
+ * Send the PendingIntent containing the corresponding SliceStore response with additional data.
+ *
+ * @param context The Context to use to send the PendingIntent.
+ * @param intent The Intent containing the PendingIntent extra.
+ * @param extra The extra to get the PendingIntent to send.
+ * @param data The Intent containing additional data to send with the PendingIntent.
+ */
+ public static void sendSliceStoreResponseWithData(@NonNull Context context,
+ @NonNull Intent intent, @NonNull String extra, @NonNull Intent data) {
+ PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+ if (pendingIntent == null) {
+ loge("PendingIntent does not exist for extra: " + extra);
+ return;
+ }
+ try {
+ pendingIntent.send(context, 0 /* unused */, data);
+ } catch (PendingIntent.CanceledException e) {
+ loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e);
+ }
+ }
+
+ /**
* Check whether the Intent is valid and can be used to complete purchases in the SliceStore.
* This checks that all necessary extras exist and that the values are valid.
*
@@ -139,7 +169,8 @@
return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED)
&& isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR)
&& isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED)
- && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA)
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_SUCCESS);
}
private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
@@ -148,12 +179,20 @@
if (pendingIntent == null) {
loge("isPendingIntentValid: " + intentType + " intent not found.");
return false;
- } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) {
- return true;
}
- loge("isPendingIntentValid: " + intentType + " intent was created by "
- + pendingIntent.getCreatorPackage() + " instead of the phone process.");
- return false;
+ String creatorPackage = pendingIntent.getCreatorPackage();
+ if (!creatorPackage.equals(TelephonyManager.PHONE_PROCESS_NAME)) {
+ String logStr = "isPendingIntentValid: " + intentType + " intent was created by "
+ + creatorPackage + " instead of the phone process.";
+ loge(logStr);
+ AnomalyReporter.reportAnomaly(UUID.fromString(UUID_BAD_PENDING_INTENT), logStr);
+ return false;
+ }
+ if (!pendingIntent.isBroadcast()) {
+ loge("isPendingIntentValid: " + intentType + " intent is not a broadcast.");
+ return false;
+ }
+ return true;
}
@NonNull private static String getPendingIntentType(@NonNull String extra) {
@@ -162,6 +201,7 @@
case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data";
+ case SliceStore.EXTRA_INTENT_SUCCESS: return "success";
default: {
loge("Unknown pending intent extra: " + extra);
return "unknown(" + extra + ")";
@@ -292,7 +332,6 @@
logd("Closing SliceStore WebView since the user did not complete the purchase "
+ "in time.");
sSliceStoreActivities.get(capability).get().finishAndRemoveTask();
- // TODO: Display a toast to indicate timeout for better UX?
}
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
new file mode 100644
index 0000000..ab5d080
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
@@ -0,0 +1,90 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.webkit.JavascriptInterface;
+
+import com.android.phone.slicestore.SliceStore;
+
+/**
+ * SliceStore web interface class allowing carrier websites to send responses back to SliceStore
+ * using JavaScript.
+ */
+public class SliceStoreWebInterface {
+ @NonNull SliceStoreActivity mActivity;
+
+ public SliceStoreWebInterface(@NonNull SliceStoreActivity activity) {
+ mActivity = activity;
+ }
+ /**
+ * Interface method allowing the carrier website to get the premium capability
+ * that was requested to purchase.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function getRequestedCapability(duration) {
+ * SliceStoreWebInterface.getRequestedCapability();
+ * }
+ * </script>
+ */
+ @JavascriptInterface
+ @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+ return mActivity.mCapability;
+ }
+
+ /**
+ * Interface method allowing the carrier website to notify the SliceStore of a successful
+ * premium capability purchase and the duration for which the premium capability is purchased.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function notifyPurchaseSuccessful(duration_ms_long = 0) {
+ * SliceStoreWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+ * }
+ * </script>
+ *
+ * @param duration The duration for which the premium capability is purchased in milliseconds.
+ */
+ @JavascriptInterface
+ public void notifyPurchaseSuccessful(long duration) {
+ mActivity.onPurchaseSuccessful(duration);
+ }
+
+ /**
+ * Interface method allowing the carrier website to notify the SliceStore of a failed
+ * premium capability purchase.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function notifyPurchaseFailed() {
+ * SliceStoreWebInterface.notifyPurchaseFailed();
+ * }
+ * </script>
+ *
+ * @param failureCode The failure code.
+ * @param failureReason If the failure code is {@link SliceStore#FAILURE_CODE_UNKNOWN},
+ * the human-readable reason for failure.
+ */
+ @JavascriptInterface
+ public void notifyPurchaseFailed(@SliceStore.FailureCode int failureCode,
+ @Nullable String failureReason) {
+ mActivity.onPurchaseFailed(failureCode, failureReason);
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0988cba..01348e4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -23,6 +23,8 @@
import android.credentials.CreateCredentialRequest
import android.credentials.ui.Constants
import android.credentials.ui.Entry
+import android.credentials.ui.CreateCredentialProviderData
+import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.credentials.ui.BaseDialogResult
@@ -54,10 +56,22 @@
RequestInfo::class.java
) ?: testRequestInfo()
- providerList = intent.extras?.getParcelableArrayList(
- ProviderData.EXTRA_PROVIDER_DATA_LIST,
- ProviderData::class.java
- ) ?: testProviderList()
+ providerList = when (requestInfo.type) {
+ RequestInfo.TYPE_CREATE ->
+ intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ CreateCredentialProviderData::class.java
+ ) ?: testCreateCredentialProviderList()
+ RequestInfo.TYPE_GET ->
+ intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData::class.java
+ ) ?: testGetCredentialProviderList()
+ else -> {
+ // TODO: fail gracefully
+ throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
+ }
+ }
resultReceiver = intent.getParcelableExtra(
Constants.EXTRA_RESULT_RECEIVER,
@@ -84,7 +98,9 @@
}
fun getCredentialInitialUiState(): GetCredentialUiState {
- val providerList = GetFlowUtils.toProviderList(providerList, context)
+ val providerList = GetFlowUtils.toProviderList(
+ // TODO: handle runtime cast error
+ providerList as List<GetCredentialProviderData>, context)
// TODO: covert from real requestInfo
val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo(
"Elisa Beckett",
@@ -100,7 +116,9 @@
}
fun createPasskeyInitialUiState(): CreatePasskeyUiState {
- val providerList = CreateFlowUtils.toProviderList(providerList, context)
+ val providerList = CreateFlowUtils.toProviderList(
+ // Handle runtime cast error
+ providerList as List<CreateCredentialProviderData>, context)
// TODO: covert from real requestInfo
val requestDisplayInfo = RequestDisplayInfo(
"Elisa Beckett",
@@ -130,32 +148,29 @@
}
// TODO: below are prototype functionalities. To be removed for productionization.
- private fun testProviderList(): List<ProviderData> {
+ private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
return listOf(
- ProviderData.Builder(
- "com.google",
- "Google Password Manager",
- Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
- .setCredentialEntries(
+ CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+ .setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
"Elisa Backett", "20 passwords and 7 passkeys saved"),
newEntry("key1", "subkey-2", "elisa.work@google.com",
"Elisa Backett Work", "20 passwords and 7 passkeys saved"),
)
- ).setActionChips(
+ )
+ .setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-1", "Go to Settings", "",
"20 passwords and 7 passkeys saved"),
newEntry("key2", "subkey-2", "Switch Account", "",
"20 passwords and 7 passkeys saved"),
),
- ).build(),
- ProviderData.Builder(
- "com.dashlane",
- "Dashlane",
- Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
- .setCredentialEntries(
+ )
+ .setIsDefaultProvider(true)
+ .build(),
+ CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ .setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
"Elisa Backett", "20 passwords and 7 passkeys saved"),
@@ -172,6 +187,42 @@
)
}
+ private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
+ return listOf(
+ GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+ .setCredentialEntries(
+ listOf<Entry>(
+ newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+ "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ newEntry("key1", "subkey-2", "elisa.work@google.com",
+ "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ )
+ ).setActionChips(
+ listOf<Entry>(
+ newEntry("key2", "subkey-1", "Go to Settings", "",
+ "20 passwords and 7 passkeys saved"),
+ newEntry("key2", "subkey-2", "Switch Account", "",
+ "20 passwords and 7 passkeys saved"),
+ ),
+ ).build(),
+ GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ .setCredentialEntries(
+ listOf<Entry>(
+ newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+ "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+ "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ )
+ ).setActionChips(
+ listOf<Entry>(
+ newEntry("key2", "subkey-3", "Manage Accounts",
+ "Manage your accounts in the dashlane app",
+ "20 passwords and 7 passkeys saved"),
+ ),
+ ).build(),
+ )
+ }
+
private fun newEntry(
key: String,
subkey: String,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 2ba8748..bf0dba2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -18,7 +18,8 @@
import android.content.Context
import android.credentials.ui.Entry
-import android.credentials.ui.ProviderData
+import android.credentials.ui.GetCredentialProviderData
+import android.credentials.ui.CreateCredentialProviderData
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.ProviderInfo
@@ -28,7 +29,7 @@
companion object {
fun toProviderList(
- providerDataList: List<ProviderData>,
+ providerDataList: List<GetCredentialProviderData>,
context: Context,
): List<ProviderInfo> {
return providerDataList.map {
@@ -36,9 +37,10 @@
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
- displayName = it.providerDisplayName,
+ // TODO: get the service display name and icon from the component name.
+ displayName = it.providerFlattenedComponentName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
+ credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context),
)
}
}
@@ -72,7 +74,7 @@
companion object {
fun toProviderList(
- providerDataList: List<ProviderData>,
+ providerDataList: List<CreateCredentialProviderData>,
context: Context,
): List<com.android.credentialmanager.createflow.ProviderInfo> {
return providerDataList.map {
@@ -80,9 +82,11 @@
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
- displayName = it.providerDisplayName,
+ // TODO: get the service display name and icon from the component name.
+ displayName = it.providerFlattenedComponentName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- createOptions = toCreationOptionInfoList(it.credentialEntries, context),
+ createOptions = toCreationOptionInfoList(it.saveEntries, context),
+ isDefault = it.isDefaultProvider,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index cb2bf10..db0f337e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -24,6 +24,7 @@
val displayName: String,
val credentialTypeIcon: Drawable,
val createOptions: List<CreateOptionInfo>,
+ val isDefault: Boolean,
)
data class CreateOptionInfo(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 93ee151..c756a17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -89,6 +89,7 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
@@ -136,6 +137,7 @@
private GlobalSettings mGlobalSettings;
private FalsingManager mFalsingManager;
private UserSwitcherController mUserSwitcherController;
+ private FalsingA11yDelegate mFalsingA11yDelegate;
private AlertDialog mAlertDialog;
private boolean mSwipeUpToRetry;
@@ -318,7 +320,8 @@
void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
- UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) {
+ UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback,
+ FalsingA11yDelegate falsingA11yDelegate) {
if (mCurrentMode == mode) return;
Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
+ modeToString(mode));
@@ -337,6 +340,7 @@
}
mGlobalSettings = globalSettings;
mFalsingManager = falsingManager;
+ mFalsingA11yDelegate = falsingA11yDelegate;
mUserSwitcherController = userSwitcherController;
setupViewMode();
}
@@ -361,7 +365,7 @@
}
mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, mFalsingA11yDelegate);
}
@Mode int getMode() {
@@ -723,7 +727,8 @@
default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {};
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {};
/** Reinitialize the location */
default void updateSecurityViewLocation() {};
@@ -828,7 +833,8 @@
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {
mView = v;
mViewFlipper = viewFlipper;
@@ -865,6 +871,7 @@
this::setupUserSwitcher;
private UserSwitcherCallback mUserSwitcherCallback;
+ private FalsingA11yDelegate mFalsingA11yDelegate;
UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
mUserSwitcherCallback = userSwitcherCallback;
@@ -874,13 +881,15 @@
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {
init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false);
mView = v;
mViewFlipper = viewFlipper;
mFalsingManager = falsingManager;
mUserSwitcherController = userSwitcherController;
mResources = v.getContext().getResources();
+ mFalsingA11yDelegate = falsingA11yDelegate;
if (mUserSwitcherViewGroup == null) {
LayoutInflater.from(v.getContext()).inflate(
@@ -978,6 +987,7 @@
mUserSwitcher.setText(currentUserName);
KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
+ anchor.setAccessibilityDelegate(mFalsingA11yDelegate);
BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
@Override
@@ -1048,7 +1058,7 @@
anchor.setOnClickListener((v) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
- mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
+ mPopup = new KeyguardUserSwitcherPopupMenu(mView.getContext(), mFalsingManager);
mPopup.setAnchorView(anchor);
mPopup.setAdapter(adapter);
mPopup.setOnItemClickListener((parent, view, pos, id) -> {
@@ -1137,7 +1147,8 @@
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {
init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true);
mView = v;
mViewFlipper = viewFlipper;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 0b395a8..79a01b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -59,6 +59,7 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -100,6 +101,7 @@
private final FeatureFlags mFeatureFlags;
private final SessionTracker mSessionTracker;
private final Optional<SidefpsController> mSidefpsController;
+ private final FalsingA11yDelegate mFalsingA11yDelegate;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -288,7 +290,8 @@
FeatureFlags featureFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
- Optional<SidefpsController> sidefpsController) {
+ Optional<SidefpsController> sidefpsController,
+ FalsingA11yDelegate falsingA11yDelegate) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -309,6 +312,7 @@
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
mSidefpsController = sidefpsController;
+ mFalsingA11yDelegate = falsingA11yDelegate;
}
@Override
@@ -349,10 +353,21 @@
if (!mSidefpsController.isPresent()) {
return;
}
- if (mBouncerVisible
- && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
- && mUpdateMonitor.isFingerprintDetectionRunning()
- && !mUpdateMonitor.userNeedsStrongAuth()) {
+ final boolean sfpsEnabled = getResources().getBoolean(
+ R.bool.config_show_sidefps_hint_on_bouncer);
+ final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
+ final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+
+ boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+
+ if (DEBUG) {
+ Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+ + "mBouncerVisible=" + mBouncerVisible + ", "
+ + "configEnabled=" + sfpsEnabled + ", "
+ + "fpsDetectionRunning=" + fpsDetectionRunning + ", "
+ + "needsStrongAuth=" + needsStrongAuth);
+ }
+ if (toShow) {
mSidefpsController.get().show();
} else {
mSidefpsController.get().hide();
@@ -625,7 +640,7 @@
mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController,
() -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue),
- null));
+ null), mFalsingA11yDelegate);
}
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
@@ -730,6 +745,7 @@
private final UserSwitcherController mUserSwitcherController;
private final SessionTracker mSessionTracker;
private final Optional<SidefpsController> mSidefpsController;
+ private final FalsingA11yDelegate mFalsingA11yDelegate;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -749,7 +765,8 @@
FeatureFlags featureFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
- Optional<SidefpsController> sidefpsController) {
+ Optional<SidefpsController> sidefpsController,
+ FalsingA11yDelegate falsingA11yDelegate) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -767,6 +784,7 @@
mUserSwitcherController = userSwitcherController;
mSessionTracker = sessionTracker;
mSidefpsController = sidefpsController;
+ mFalsingA11yDelegate = falsingA11yDelegate;
}
public KeyguardSecurityContainerController create(
@@ -777,7 +795,7 @@
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker,
- mSidefpsController);
+ mSidefpsController, mFalsingA11yDelegate);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 22dc94a..5850c95 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.os.Handler
import android.os.Looper
+import android.os.Trace
import android.os.UserHandle
import android.util.ArrayMap
import android.util.ArraySet
@@ -126,6 +127,7 @@
action,
userId,
{
+ Trace.beginSection("registerReceiver act=$action user=$userId")
context.registerReceiverAsUser(
this,
UserHandle.of(userId),
@@ -134,11 +136,14 @@
workerHandler,
flags
)
+ Trace.endSection()
logger.logContextReceiverRegistered(userId, flags, it)
},
{
try {
+ Trace.beginSection("unregisterReceiver act=$action user=$userId")
context.unregisterReceiver(this)
+ Trace.endSection()
logger.logContextReceiverUnregistered(userId, action)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Trying to unregister unregistered receiver for user $userId, " +
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 500f280..2245d84 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -337,7 +337,8 @@
|| mTestHarness
|| mDataProvider.isJustUnlockedWithFace()
|| mDataProvider.isDocked()
- || mAccessibilityManager.isTouchExplorationEnabled();
+ || mAccessibilityManager.isTouchExplorationEnabled()
+ || mDataProvider.isA11yAction();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
new file mode 100644
index 0000000..63d57cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK
+import javax.inject.Inject
+
+/**
+ * Class that injects an artificial tap into the falsing collector.
+ *
+ * This is used for views that can be interacted with by A11y services and have falsing checks, as
+ * the gestures made by the A11y framework do not propagate motion events down the view hierarchy.
+ */
+class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
+ View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ if (action == ACTION_CLICK) {
+ falsingCollector.onA11yAction()
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 858bac3..6670108 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -132,5 +132,8 @@
/** */
void updateFalseConfidence(FalsingClassifier.Result result);
+
+ /** Indicates an a11y action was made. */
+ void onA11yAction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 0b7d6ab..cc25368 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -157,4 +157,8 @@
@Override
public void updateFalseConfidence(FalsingClassifier.Result result) {
}
+
+ @Override
+ public void onA11yAction() {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index da3d293..8bdef13 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -375,6 +375,15 @@
mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
}
+ @Override
+ public void onA11yAction() {
+ if (mPendingDownEvent != null) {
+ mPendingDownEvent.recycle();
+ mPendingDownEvent = null;
+ }
+ mFalsingDataProvider.onA11yAction();
+ }
+
private boolean shouldSessionBeActive() {
return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 3991a35..09ebeea 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -59,6 +59,7 @@
private MotionEvent mFirstRecentMotionEvent;
private MotionEvent mLastMotionEvent;
private boolean mJustUnlockedWithFace;
+ private boolean mA11YAction;
@Inject
public FalsingDataProvider(
@@ -124,6 +125,7 @@
mPriorMotionEvents = mRecentMotionEvents;
mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
+ mA11YAction = false;
}
/** Returns screen width in pixels. */
@@ -334,6 +336,17 @@
mGestureFinalizedListeners.remove(listener);
}
+ /** Return whether last gesture was an A11y action. */
+ public boolean isA11yAction() {
+ return mA11YAction;
+ }
+
+ /** Set whether last gesture was an A11y action. */
+ public void onA11yAction() {
+ completePriorGesture();
+ this.mA11YAction = true;
+ }
+
void onSessionStarted() {
mSessionListeners.forEach(SessionListener::onSessionStarted);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index bf7d716..6cb0e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -24,7 +24,6 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.SharedPreferences
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
@@ -59,7 +58,10 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -76,13 +78,14 @@
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
- @Main val sharedPreferences: SharedPreferences,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val shadeController: ShadeController,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
- private val keyguardStateController: KeyguardStateController
+ private val keyguardStateController: KeyguardStateController,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
) : ControlsUiController {
companion object {
@@ -110,6 +113,12 @@
private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
+ private val sharedPreferences
+ get() = userFileManager.getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = 0,
+ userId = userTracker.userId
+ )
private val collator = Collator.getInstance(context.resources.configuration.locales[0])
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 48bef97..2bee75e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -41,6 +41,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
@@ -93,6 +94,7 @@
AospPolicyModule.class,
GestureModule.class,
MediaModule.class,
+ MultiUserUtilsModule.class,
PowerModule.class,
QSModule.class,
ReferenceScreenshotModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6db56210..482bdaf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -58,7 +58,6 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
@@ -140,7 +139,6 @@
PrivacyModule.class,
ScreenshotModule.class,
SensorModule.class,
- MultiUserUtilsModule.class,
SecurityRepositoryModule.class,
SettingsUtilModule.class,
SmartRepliesInflationModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7b1ba0d..b5d4ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -308,7 +308,7 @@
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
- @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true)
// TODO(b/254513155): Tracking Bug
@JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 6711734..cd5647e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -30,7 +30,6 @@
import androidx.annotation.WorkerThread
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE
import com.android.systemui.util.Assert
import java.io.PrintWriter
import java.lang.ref.WeakReference
@@ -53,7 +52,7 @@
*
* Class constructed and initialized in [SettingsModule].
*/
-class UserTrackerImpl internal constructor(
+open class UserTrackerImpl internal constructor(
private val context: Context,
private val userManager: UserManager,
private val dumpManager: DumpManager,
@@ -70,13 +69,13 @@
private val mutex = Any()
override var userId: Int by SynchronizedDelegate(context.userId)
- private set
+ protected set
override var userHandle: UserHandle by SynchronizedDelegate(context.user)
- private set
+ protected set
override var userContext: Context by SynchronizedDelegate(context)
- private set
+ protected set
override val userContentResolver: ContentResolver
get() = userContext.contentResolver
@@ -94,7 +93,7 @@
* modified.
*/
override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList())
- private set
+ protected set
@GuardedBy("callbacks")
private val callbacks: MutableList<DataItem> = ArrayList()
@@ -155,7 +154,7 @@
}
@WorkerThread
- private fun handleSwitchUser(newUser: Int) {
+ protected open fun handleSwitchUser(newUser: Int) {
Assert.isNotMainThread()
if (newUser == UserHandle.USER_NULL) {
Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
@@ -174,7 +173,7 @@
}
@WorkerThread
- private fun handleProfilesChanged() {
+ protected open fun handleProfilesChanged() {
Assert.isNotMainThread()
val profiles = userManager.getProfiles(userId)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 760d22ef..2450197 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -33,7 +33,6 @@
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.NotificationPanelView.DEBUG;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -42,7 +41,6 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -52,10 +50,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
@@ -103,7 +101,6 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
@@ -174,16 +171,13 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -209,7 +203,6 @@
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -256,28 +249,15 @@
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
-
private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false);
-
- /**
- * The parallax amount of the quick settings translation when dragging down the panel
- */
+ /** The parallax amount of the quick settings translation when dragging down the panel. */
private static final float QS_PARALLAX_AMOUNT = 0.175f;
-
- /**
- * Fling expanding QS.
- */
+ /** Fling expanding QS. */
public static final int FLING_EXPAND = 0;
-
- /**
- * Fling collapsing QS, potentially stopping when QS becomes QQS.
- */
+ /** Fling collapsing QS, potentially stopping when QS becomes QQS. */
private static final int FLING_COLLAPSE = 1;
-
- /**
- * Fling until QS is completely hidden.
- */
+ /** Fling until QS is completely hidden. */
private static final int FLING_HIDE = 2;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
@@ -291,6 +271,18 @@
* when flinging. A low value will make it that most flings will reach the maximum overshoot.
*/
private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
+ /**
+ * Maximum time before which we will expand the panel even for slow motions when getting a
+ * touch passed over from launcher.
+ */
+ private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
+ private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
+ private static final String COUNTER_PANEL_OPEN = "panel_open";
+ private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+ private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+ private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
+ private static final Rect EMPTY_RECT = new Rect();
+
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
private final Resources mResources;
private final KeyguardStateController mKeyguardStateController;
@@ -299,49 +291,24 @@
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final SystemClock mSystemClock;
private final ShadeLogger mShadeLog;
-
private final DozeParameters mDozeParameters;
- private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
- private final Runnable mCollapseExpandAction = new CollapseExpandAction();
- private final OnOverscrollTopChangedListener
- mOnOverscrollTopChangedListener =
- new OnOverscrollTopChangedListener();
- private final OnEmptySpaceClickListener
- mOnEmptySpaceClickListener =
- new OnEmptySpaceClickListener();
- private final MyOnHeadsUpChangedListener
- mOnHeadsUpChangedListener =
- new MyOnHeadsUpChangedListener();
- private final HeightListener mHeightListener = new HeightListener();
+ private final Runnable mCollapseExpandAction = this::collapseOrExpand;
+ private final NsslOverscrollTopChangedListener mOnOverscrollTopChangedListener =
+ new NsslOverscrollTopChangedListener();
+ private final NotificationStackScrollLayout.OnEmptySpaceClickListener
+ mOnEmptySpaceClickListener = (x, y) -> onEmptySpaceClick();
+ private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener =
+ new ShadeHeadsUpChangedListener();
+ private final QS.HeightListener mHeightListener = this::onQsHeightChanged;
private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
private final SettingsChangeObserver mSettingsChangeObserver;
-
- @VisibleForTesting
- final StatusBarStateListener mStatusBarStateListener =
- new StatusBarStateListener();
+ private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
private final MetricsLogger mMetricsLogger;
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
- private final NotificationIconAreaController mNotificationIconAreaController;
-
- /**
- * Maximum time before which we will expand the panel even for slow motions when getting a
- * touch passed over from launcher.
- */
- private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
-
- private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
-
- private static final String COUNTER_PANEL_OPEN = "panel_open";
- private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
- private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
-
- private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
- private static final Rect EMPTY_RECT = new Rect();
-
private final InteractionJankMonitor mInteractionJankMonitor;
private final LayoutInflater mLayoutInflater;
private final FeatureFlags mFeatureFlags;
@@ -361,9 +328,7 @@
private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
private final FragmentService mFragmentService;
private final ScrimController mScrimController;
- private final PrivacyDotViewController mPrivacyDotViewController;
private final NotificationRemoteInputManager mRemoteInputManager;
-
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final ShadeTransitionController mShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
@@ -380,6 +345,11 @@
private final Interpolator mBounceInterpolator;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
+ private final QS.ScrollListener mQsScrollListener = this::onQsPanelScrollChanged;
+ private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
+ private final FragmentListener mQsFragmentListener = new QsFragmentListener();
+ private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
private boolean mIsLaunchAnimationRunning;
@@ -401,13 +371,11 @@
private float mKeyguardNotificationTopPadding;
/** Current max allowed keyguard notifications determined by measuring the panel. */
private int mMaxAllowedKeyguardNotifications;
-
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
- @VisibleForTesting
- QS mQs;
+ private QS mQs;
private FrameLayout mQsFrame;
private final QsFrameTranslateController mQsFrameTranslateController;
private KeyguardStatusViewController mKeyguardStatusViewController;
@@ -420,18 +388,11 @@
private float mQuickQsHeaderHeight;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
private int mQsTrackingPointer;
private VelocityTracker mQsVelocityTracker;
private boolean mQsTracking;
-
- /**
- * If set, the ongoing touch gesture might both trigger the expansion in {@link
- * NotificationPanelView} and
- * the expansion for quick settings.
- */
+ /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
private boolean mConflictingQsExpansionGesture;
-
private boolean mPanelExpanded;
/**
@@ -486,11 +447,9 @@
* Used for split shade, two finger gesture as well as accessibility shortcut to QS.
* It needs to be set when movement starts as it resets at the end of expansion/collapse.
*/
- @VisibleForTesting
- boolean mQsExpandImmediate;
+ private boolean mQsExpandImmediate;
private boolean mTwoFingerQsExpandPossible;
private String mHeaderDebugInfo;
-
/**
* If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
* need to take this into account in our panel height calculation.
@@ -498,7 +457,6 @@
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
private ValueAnimator mQsSizeChangeAnimator;
-
private boolean mQsScrimEnabled = true;
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
@@ -516,39 +474,27 @@
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
- private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
- setHeadsUpAnimatingAway(false);
- updatePanelExpansionAndVisibility();
- };
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
private int mAmbientIndicationBottomPadding;
+ /** Whether the notifications are displayed full width (no margins on the side). */
private boolean mIsFullWidth;
private boolean mBlockingExpansionForCurrentTouch;
+ // Following variables maintain state of events when input focus transfer may occur.
+ private boolean mExpectingSynthesizedDown;
+ private boolean mLastEventSynthesizedDown;
- /**
- * Following variables maintain state of events when input focus transfer may occur.
- */
- private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
- private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
-
- /**
- * Current dark amount that follows regular interpolation curve of animation.
- */
+ /** Current dark amount that follows regular interpolation curve of animation. */
private float mInterpolatedDarkAmount;
-
/**
* Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
* interpolation curve is different.
*/
private float mLinearDarkAmount;
-
private boolean mPulsing;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
- /**
- * Non-null if there's a heads-up notification that we're currently tracking the position of.
- */
+ /** Non-null if a heads-up notification's position is being tracked. */
@Nullable
private ExpandableNotificationRow mTrackedHeadsUpNotification;
private final ArrayList<Consumer<ExpandableNotificationRow>>
@@ -578,8 +524,9 @@
private final CommandQueue mCommandQueue;
private final UserManager mUserManager;
private final MediaDataManager mMediaDataManager;
+ @PanelState
+ private int mCurrentPanelState = STATE_CLOSED;
private final SysUiState mSysUiState;
-
private final NotificationShadeDepthController mDepthController;
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
@@ -589,6 +536,7 @@
private boolean mHeadsUpPinnedMode;
private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable;
+ private Runnable mHideExpandedRunnable;
/**
* The padding between the start of notifications and the qs boundary on the lockscreen.
@@ -596,94 +544,51 @@
* qs boundary to be padded.
*/
private int mLockscreenNotificationQSPadding;
-
/**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade. This value can also go beyond 1.1 when we're overshooting!
*/
private float mTransitioningToFullShadeProgress;
-
/**
* Position of the qs bottom during the full shade transition. This is needed as the toppadding
* can change during state changes, which makes it much harder to do animations
*/
private int mTransitionToFullShadeQSPosition;
-
- /**
- * Distance that the full shade transition takes in order for qs to fully transition to the
- * shade.
- */
+ /** Distance a full shade transition takes in order for qs to fully transition to the shade. */
private int mDistanceForQSFullShadeTransition;
-
- /**
- * The translation amount for QS for the full shade transition
- */
+ /** The translation amount for QS for the full shade transition. */
private float mQsTranslationForFullShadeTransition;
- /**
- * The maximum overshoot allowed for the top padding for the full shade transition
- */
+ /** The maximum overshoot allowed for the top padding for the full shade transition. */
private int mMaxOverscrollAmountForPulse;
-
- /**
- * Should we animate the next bounds update
- */
+ /** Should we animate the next bounds update. */
private boolean mAnimateNextNotificationBounds;
- /**
- * The delay for the next bounds animation
- */
+ /** The delay for the next bounds animation. */
private long mNotificationBoundsAnimationDelay;
-
- /**
- * The duration of the notification bounds animation
- */
+ /** The duration of the notification bounds animation. */
private long mNotificationBoundsAnimationDuration;
- /**
- * Is this a collapse that started on the panel where we should allow the panel to intercept
- */
+ /** Whether a collapse that started on the panel should allow the panel to intercept. */
private boolean mIsPanelCollapseOnQQS;
-
private boolean mAnimatingQS;
-
- /**
- * The end bounds of a clipping animation.
- */
+ /** The end bounds of a clipping animation. */
private final Rect mQsClippingAnimationEndBounds = new Rect();
-
- /**
- * The animator for the qs clipping bounds.
- */
+ /** The animator for the qs clipping bounds. */
private ValueAnimator mQsClippingAnimation = null;
-
- /**
- * Is the current animator resetting the qs translation.
- */
+ /** Whether the current animator is resetting the qs translation. */
private boolean mIsQsTranslationResetAnimator;
- /**
- * Is the current animator resetting the pulse expansion after a drag down
- */
+ /** Whether the current animator is resetting the pulse expansion after a drag down. */
private boolean mIsPulseExpansionResetAnimator;
private final Rect mKeyguardStatusAreaClipBounds = new Rect();
private final Region mQsInterceptRegion = new Region();
-
- /**
- * The alpha of the views which only show on the keyguard but not in shade / shade locked
- */
+ /** Alpha of the views which only show on the keyguard but not in shade / shade locked. */
private float mKeyguardOnlyContentAlpha = 1.0f;
-
- /**
- * The translationY of the views which only show on the keyguard but in shade / shade locked.
- */
+ /** Y translation of the views that only show on the keyguard but in shade / shade locked. */
private int mKeyguardOnlyTransitionTranslationY = 0;
-
private float mUdfpsMaxYBurnInOffset;
-
- /**
- * Are we currently in gesture navigation
- */
+ /** Are we currently in gesture navigation. */
private boolean mIsGestureNavigation;
private int mOldLayoutDirection;
private NotificationShelfController mNotificationShelfController;
@@ -696,6 +601,7 @@
private int mQsClipTop;
private int mQsClipBottom;
private boolean mQsVisible;
+
private final ContentResolver mContentResolver;
private float mMinFraction;
@@ -714,55 +620,7 @@
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-
private final NPVCDownEventState.Buffer mLastDownEvents;
-
- private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
- () -> mKeyguardBottomArea.setVisibility(View.GONE);
-
- private final AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host,
- AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action
- == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
- || action
- == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
- mStatusBarKeyguardViewManager.showBouncer(true);
- return true;
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
-
- private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
- @Override
- public void onAdditionalTapRequired() {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
- mTapAgainViewController.show();
- } else {
- mKeyguardIndicationController.showTransientIndication(
- R.string.notification_tap_again);
- }
-
- if (!mStatusBarStateController.isDozing()) {
- mVibratorHelper.vibrate(
- Process.myUid(),
- mView.getContext().getPackageName(),
- ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
- "falsing-additional-tap-required",
- TOUCH_VIBRATION_ATTRIBUTES);
- }
- }
- };
-
private final CameraGestureHelper mCameraGestureHelper;
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -812,8 +670,20 @@
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
+
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
+ private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
+ () -> mKeyguardBottomArea.setVisibility(View.GONE);
+ private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
+ setHeadsUpAnimatingAway(false);
+ updatePanelExpansionAndVisibility();
+ };
+ private final Runnable mMaybeHideExpandedRunnable = () -> {
+ if (getExpansionFraction() == 0.0f) {
+ getView().post(mHideExpandedRunnable);
+ }
+ };
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -848,7 +718,6 @@
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
- NotificationIconAreaController notificationIconAreaController,
AuthController authController,
ScrimController scrimController,
UserManager userManager,
@@ -857,7 +726,6 @@
AmbientState ambientState,
LockIconViewController lockIconViewController,
KeyguardMediaController keyguardMediaController,
- PrivacyDotViewController privacyDotViewController,
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
NavigationBarController navigationBarController,
@@ -895,7 +763,6 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
- TouchHandler touchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -903,13 +770,12 @@
}
@Override
- public void onViewDetachedFromWindow(View v) {
- }
+ public void onViewDetachedFromWindow(View v) {}
});
- mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(touchHandler);
- mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+ mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener());
+ mView.setOnTouchListener(createTouchHandler());
+ mView.setOnConfigurationChangedListener(config -> loadDimens());
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
@@ -945,7 +811,6 @@
mInteractionJankMonitor = interactionJankMonitor;
mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
- mPrivacyDotViewController = privacyDotViewController;
mMetricsLogger = metricsLogger;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
@@ -957,7 +822,6 @@
mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
- mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mDepthController = notificationShadeDepthController;
@@ -1000,10 +864,7 @@
mShadeTransitionController = shadeTransitionController;
lockscreenShadeTransitionController.setNotificationPanelController(this);
shadeTransitionController.setNotificationPanelViewController(this);
- DynamicPrivacyControlListener
- dynamicPrivacyControlListener =
- new DynamicPrivacyControlListener();
- dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
+ dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
@@ -1027,13 +888,14 @@
mIsGestureNavigation = QuickStepContract.isGesturalMode(currentMode);
mView.setBackgroundColor(Color.TRANSPARENT);
- OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
+ ShadeAttachStateChangeListener
+ onAttachStateChangeListener = new ShadeAttachStateChangeListener();
mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
if (mView.isAttachedToWindow()) {
onAttachStateChangeListener.onViewAttachedToWindow(mView);
}
- mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
+ mView.setOnApplyWindowInsetsListener((v, insets) -> onApplyShadeWindowInsets(insets));
if (DEBUG_DRAWABLE) {
mView.getOverlay().add(new DebugDrawable());
@@ -1052,57 +914,68 @@
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@Override
public void onUnlockAnimationFinished() {
- // Make sure the clock is in the correct position after the unlock animation
- // so that it's not in the wrong place when we show the keyguard again.
- positionClockAndNotifications(true /* forceClockUpdate */);
+ unlockAnimationFinished();
}
@Override
public void onUnlockAnimationStarted(
boolean playingCannedAnimation,
boolean isWakeAndUnlock,
- long unlockAnimationStartDelay,
+ long startDelay,
long unlockAnimationDuration) {
- // Disable blurs while we're unlocking so that panel expansion does not
- // cause blurring. This will eventually be re-enabled by the panel view on
- // ACTION_UP, since the user's finger might still be down after a swipe to
- // unlock gesture, and we don't want that to cause blurring either.
- mDepthController.setBlursDisabledForUnlock(mTracking);
-
- if (playingCannedAnimation && !isWakeAndUnlock) {
- // Hide the panel so it's not in the way or the surface behind the
- // keyguard, which will be appearing. If we're wake and unlocking, the
- // lock screen is hidden instantly so should not be flung away.
- if (isTracking() || isFlinging()) {
- // Instant collpase the notification panel since the notification
- // panel is already in the middle animating
- onTrackingStopped(false);
- instantCollapse();
- } else {
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- // Translate up by 4%.
- .translationY(mView.getHeight() * -0.04f)
- // This start delay is to give us time to animate out before
- // the launcher icons animation starts, so use that as our
- // duration.
- .setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_ACCELERATE)
- .withEndAction(() -> {
- instantCollapse();
- mView.setAlpha(1f);
- mView.setTranslationY(0f);
- })
- .start();
- }
- }
+ unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
mCameraGestureHelper = cameraGestureHelper;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
}
+ private void unlockAnimationFinished() {
+ // Make sure the clock is in the correct position after the unlock animation
+ // so that it's not in the wrong place when we show the keyguard again.
+ positionClockAndNotifications(true /* forceClockUpdate */);
+ }
+
+ private void unlockAnimationStarted(
+ boolean playingCannedAnimation,
+ boolean isWakeAndUnlock,
+ long unlockAnimationStartDelay) {
+ // Disable blurs while we're unlocking so that panel expansion does not
+ // cause blurring. This will eventually be re-enabled by the panel view on
+ // ACTION_UP, since the user's finger might still be down after a swipe to
+ // unlock gesture, and we don't want that to cause blurring either.
+ mDepthController.setBlursDisabledForUnlock(mTracking);
+
+ if (playingCannedAnimation && !isWakeAndUnlock) {
+ // Hide the panel so it's not in the way or the surface behind the
+ // keyguard, which will be appearing. If we're wake and unlocking, the
+ // lock screen is hidden instantly so should not be flung away.
+ if (isTracking() || mIsFlinging) {
+ // Instant collapse the notification panel since the notification
+ // panel is already in the middle animating
+ onTrackingStopped(false);
+ instantCollapse();
+ } else {
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ // Translate up by 4%.
+ .translationY(mView.getHeight() * -0.04f)
+ // This start delay is to give us time to animate out before
+ // the launcher icons animation starts, so use that as our
+ // duration.
+ .setDuration(unlockAnimationStartDelay)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
+ .withEndAction(() -> {
+ instantCollapse();
+ mView.setAlpha(1f);
+ mView.setTranslationY(0f);
+ })
+ .start();
+ }
+ }
+ }
+
@VisibleForTesting
void onFinishInflate() {
loadDimens();
@@ -1139,7 +1012,7 @@
R.id.notification_stack_scroller);
mNotificationStackScrollLayoutController.attach(stackScrollLayout);
mNotificationStackScrollLayoutController.setOnHeightChangedListener(
- mOnHeightChangedListener);
+ new NsslHeightChangedListener());
mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
mOnOverscrollTopChangedListener);
mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
@@ -1260,11 +1133,6 @@
}
}
- private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
- // TODO: this can be injected.
- mCentralSurfaces = centralSurfaces;
- }
-
public void updateResources() {
mSplitShadeNotificationsScrimMarginBottom =
mResources.getDimensionPixelSize(
@@ -1350,7 +1218,7 @@
@VisibleForTesting
void reInflateViews() {
- if (DEBUG_LOGCAT) Log.d(TAG, "reInflateViews");
+ debugLog("reInflateViews");
// Re-inflate the status view group.
KeyguardStatusView keyguardStatusView =
mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
@@ -1429,6 +1297,11 @@
mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
}
+ @VisibleForTesting
+ void setQs(QS qs) {
+ mQs = qs;
+ }
+
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
mKeyguardMediaController.attachSplitShadeContainer(container);
}
@@ -1443,12 +1316,7 @@
}
@VisibleForTesting
- boolean getClosing() {
- return mClosing;
- }
-
- @VisibleForTesting
- boolean getIsFlinging() {
+ boolean isFlinging() {
return mIsFlinging;
}
@@ -1923,13 +1791,13 @@
setQsExpandImmediate(true);
setShowShelfOnly(true);
}
- if (DEBUG) this.logf("collapse: " + this);
+ debugLog("collapse: %s", this);
if (canPanelBeCollapsed()) {
cancelHeightAnimator();
notifyExpandingStarted();
// Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- setIsClosing(true);
+ setClosing(true);
if (delayed) {
mNextCollapseSpeedUpFactor = speedUpFactor;
this.mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -1939,13 +1807,19 @@
}
}
- private void setQsExpandImmediate(boolean expandImmediate) {
+ @VisibleForTesting
+ void setQsExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mQsExpandImmediate) {
mQsExpandImmediate = expandImmediate;
mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
}
}
+ @VisibleForTesting
+ boolean isQsExpandImmediate() {
+ return mQsExpandImmediate;
+ }
+
private void setShowShelfOnly(boolean shelfOnly) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
shelfOnly && !mSplitShadeEnabled);
@@ -2032,12 +1906,12 @@
}
}
- public void fling(float vel, boolean expand) {
+ private void fling(float vel) {
GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
if (gr != null) {
gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
}
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+ fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false);
}
@VisibleForTesting
@@ -2124,7 +1998,7 @@
@Override
public void onAnimationEnd(Animator animation) {
if (shouldSpringBack && !mCancelled) {
- // After the shade is flinged open to an overscrolled state, spring back
+ // After the shade is flung open to an overscrolled state, spring back
// the shade by reducing section padding to 0.
springBack();
} else {
@@ -2154,7 +2028,7 @@
}
private boolean onQsIntercept(MotionEvent event) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept");
+ debugLog("onQsIntercept");
int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -2215,7 +2089,7 @@
if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
+ debugLog("onQsIntercept - start tracking expansion");
mView.getParent().requestDisallowInterceptTouchEvent(true);
mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
mQsTracking = true;
@@ -2274,7 +2148,7 @@
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
- mDozingOnDown = isDozing();
+ mDozingOnDown = mDozing;
mDownX = event.getX();
mDownY = event.getY();
mCollapsedOnDown = isFullyCollapsed();
@@ -2324,7 +2198,7 @@
float vel = getCurrentQSVelocity();
boolean expandsQs = flingExpandsQs(vel);
if (expandsQs) {
- if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
+ if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
expandsQs = false;
} else {
logQsSwipeDown(y);
@@ -2363,9 +2237,9 @@
}
}
- private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+ private boolean isFalseTouch() {
if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch(interactionType);
+ return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS);
}
return !mQsTouchAboveFalsingThreshold;
}
@@ -2491,7 +2365,7 @@
private void handleQsDown(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
event.getX(), event.getY(), -1)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown");
+ debugLog("handleQsDown");
mFalsingCollector.onQsDown();
mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
mQsTracking = true;
@@ -2505,9 +2379,7 @@
}
}
- /**
- * Input focus transfer is about to happen.
- */
+ /** Input focus transfer is about to happen. */
public void startWaitingForOpenPanelGesture() {
if (!isFullyCollapsed()) {
return;
@@ -2539,7 +2411,7 @@
} else {
// Window never will receive touch events that typically trigger haptic on open.
maybeVibrateOnOpening(false /* openingWithTouch */);
- fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
+ fling(velocity > 1f ? 1000f * velocity : 0 /* expand */);
}
onTrackingStopped(false);
}
@@ -2613,7 +2485,7 @@
break;
case MotionEvent.ACTION_MOVE:
- if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move");
+ debugLog("onQSTouch move");
mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
setQsExpansionHeight(h + mInitialHeightOnTouch);
if (h >= getFalsingThreshold()) {
@@ -2690,6 +2562,9 @@
navigationBarView.onStatusBarPanelStateChanged();
}
mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+ mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
+ mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling,
+ mDozing, mQsAnimatorExpand, mAnimatingQS);
}
}
@@ -2901,7 +2776,7 @@
}
private int calculateLeftQsClippingBound() {
- if (isFullWidth()) {
+ if (mIsFullWidth) {
// left bounds can ignore insets, it should always reach the edge of the screen
return 0;
} else {
@@ -2910,7 +2785,7 @@
}
private int calculateRightQsClippingBound() {
- if (isFullWidth()) {
+ if (mIsFullWidth) {
return getView().getRight() + mDisplayRightInset;
} else {
return mNotificationStackScrollLayoutController.getRight();
@@ -2978,7 +2853,7 @@
// Fancy clipping for quick settings
int radius = mScrimCornerRadius;
boolean clipStatusView = false;
- if (isFullWidth()) {
+ if (mIsFullWidth) {
// The padding on this area is large enough that we can use a cheaper clipping strategy
mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
clipStatusView = qsVisible;
@@ -3128,10 +3003,7 @@
}
}
- /**
- * @return the topPadding of notifications when on keyguard not respecting quick settings
- * expansion
- */
+ /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
private int getKeyguardNotificationStaticPadding() {
if (!mKeyguardShowing) {
return 0;
@@ -3163,7 +3035,7 @@
* shade. 0.0f means we're not transitioning yet.
*/
public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
- if (animate && isFullWidth()) {
+ if (animate && mIsFullWidth) {
animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
delay);
mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
@@ -3212,10 +3084,7 @@
updateQsExpansion();
}
- /**
- * Notify the panel that the pulse expansion has finished and that we're going to the full
- * shade
- */
+ /** Called when pulse expansion has finished and this is going to the full shade. */
public void onPulseExpansionFinished() {
animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
mIsPulseExpansionResetAnimator = true;
@@ -3270,9 +3139,7 @@
}
}
- /**
- * @see #flingSettings(float, int, Runnable, boolean)
- */
+ /** @see #flingSettings(float, int, Runnable, boolean) */
public void flingSettings(float vel, int type) {
flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
@@ -3405,7 +3272,8 @@
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
- public int getMaxPanelHeight() {
+ @VisibleForTesting
+ int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
&& mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
@@ -3439,13 +3307,20 @@
}
private void onHeightUpdated(float expandedHeight) {
+ if (expandedHeight <= 0) {
+ mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ } else if (isFullyExpanded()) {
+ mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ }
if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
// This is a circular dependency and should be avoided, otherwise we'll have
// a stack overflow.
if (mStackScrollerMeasuringPass > 2) {
- if (DEBUG_LOGCAT) Log.d(TAG, "Unstable notification panel height. Aborting.");
+ debugLog("Unstable notification panel height. Aborting.");
} else {
positionClockAndNotifications();
}
@@ -3581,9 +3456,7 @@
return alpha;
}
- /**
- * Hides the header when notifications are colliding with it.
- */
+ /** Hides the header when notifications are colliding with it. */
private void updateHeader() {
if (mBarState == KEYGUARD) {
mKeyguardStatusBarViewController.updateViewState();
@@ -3726,7 +3599,7 @@
if (mAnimateAfterExpanding) {
notifyExpandingStarted();
beginJankMonitoring();
- fling(0, true /* expand */);
+ fling(0 /* expand */);
} else {
setExpandedFraction(1f);
}
@@ -3763,6 +3636,24 @@
}
+ private void falsingAdditionalTapRequired() {
+ if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
+ mTapAgainViewController.show();
+ } else {
+ mKeyguardIndicationController.showTransientIndication(
+ R.string.notification_tap_again);
+ }
+
+ if (!mStatusBarStateController.isDozing()) {
+ mVibratorHelper.vibrate(
+ Process.myUid(),
+ mView.getContext().getPackageName(),
+ ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
+ "falsing-additional-tap-required",
+ TOUCH_VIBRATION_ATTRIBUTES);
+ }
+ }
+
private void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
endClosing();
@@ -3797,7 +3688,7 @@
private void updateMaxHeadsUpTranslation() {
mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
- getHeight(), mNavigationBarBottomHeight);
+ mView.getHeight(), mNavigationBarBottomHeight);
}
@VisibleForTesting
@@ -3842,7 +3733,8 @@
|| !isTracking());
}
- public int getMaxPanelTransitionDistance() {
+ @VisibleForTesting
+ int getMaxPanelTransitionDistance() {
// Traditionally the value is based on the number of notifications. On split-shade, we want
// the required distance to be a specific and constant value, to make sure the expansion
// motion has the expected speed. We also only want this on non-lockscreen for now.
@@ -3898,10 +3790,9 @@
}
@VisibleForTesting
- void setIsClosing(boolean isClosing) {
- boolean wasClosing = isClosing();
- mClosing = isClosing;
- if (wasClosing != isClosing) {
+ void setClosing(boolean isClosing) {
+ if (mClosing != isClosing) {
+ mClosing = isClosing;
mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
}
mAmbientState.setIsClosing(isClosing);
@@ -3914,10 +3805,6 @@
}
}
- public boolean isDozing() {
- return mDozing;
- }
-
public void setQsScrimEnabled(boolean qsScrimEnabled) {
boolean changed = mQsScrimEnabled != qsScrimEnabled;
mQsScrimEnabled = qsScrimEnabled;
@@ -3930,7 +3817,7 @@
mKeyguardStatusViewController.dozeTimeTick();
}
- private boolean onMiddleClicked() {
+ private void onMiddleClicked() {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
@@ -3952,14 +3839,12 @@
startUnlockHintAnimation();
}
}
- return true;
+ break;
case StatusBarState.SHADE_LOCKED:
if (!mQsExpanded) {
mStatusBarStateController.setState(KEYGUARD);
}
- return true;
- default:
- return true;
+ break;
}
}
@@ -4033,17 +3918,9 @@
updateStatusBarIcons();
}
- /**
- * @return whether the notifications are displayed full width and don't have any margins on
- * the side.
- */
- public boolean isFullWidth() {
- return mIsFullWidth;
- }
-
private void updateStatusBarIcons() {
boolean showIconsWhenExpanded =
- (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+ (isPanelVisibleBecauseOfHeadsUp() || mIsFullWidth)
&& getExpandedHeight() < getOpeningHeight();
if (showIconsWhenExpanded && isOnKeyguard()) {
showIconsWhenExpanded = false;
@@ -4058,10 +3935,7 @@
return mBarState == KEYGUARD;
}
- /**
- * Called when heads-up notification is being dragged up or down to indicate what's the starting
- * height for shade motion
- */
+ /** Called when a HUN is dragged up or down to indicate the starting height for shade motion. */
public void setHeadsUpDraggingStartingHeight(int startHeight) {
mHeadsUpStartHeight = startHeight;
float scrimMinFraction;
@@ -4115,25 +3989,18 @@
setLaunchingAffordance(false);
}
- /**
- * Set whether we are currently launching an affordance. This is currently only set when
- * launched via a camera gesture.
- */
+ /** Set whether we are currently launching an affordance (i.e. camera gesture). */
private void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
}
- /**
- * Return true when a bottom affordance is launching an occluded activity with a splash screen.
- */
+ /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */
public boolean isLaunchingAffordanceWithPreview() {
return mLaunchingAffordance;
}
- /**
- * Whether the camera application can be launched for the camera launch gesture.
- */
+ /** Whether the camera application can be launched by the camera launch gesture. */
public boolean canCameraGestureBeLaunched() {
return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
}
@@ -4146,22 +4013,19 @@
&& mHeadsUpAppearanceController.shouldBeVisible()) {
return false;
}
- return !isFullWidth() || !mShowIconsWhenExpanded;
+ return !mIsFullWidth || !mShowIconsWhenExpanded;
}
- public final QS.ScrollListener mScrollListener = new QS.ScrollListener() {
- @Override
- public void onQsPanelScrollChanged(int scrollY) {
- mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
- if (scrollY > 0 && !mQsFullyExpanded) {
- if (DEBUG_LOGCAT) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
- // If we are scrolling QS, we should be fully expanded.
- expandWithQs();
- }
+ private void onQsPanelScrollChanged(int scrollY) {
+ mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
+ if (scrollY > 0 && !mQsFullyExpanded) {
+ debugLog("Scrolling while not expanded. Forcing expand");
+ // If we are scrolling QS, we should be fully expanded.
+ expandWithQs();
}
- };
+ }
- private final FragmentListener mFragmentListener = new FragmentListener() {
+ private final class QsFragmentListener implements FragmentListener {
@Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
@@ -4178,7 +4042,7 @@
final int height = bottom - top;
final int oldHeight = oldBottom - oldTop;
if (height != oldHeight) {
- mHeightListener.onQsHeightChanged();
+ onQsHeightChanged();
}
});
mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
@@ -4191,7 +4055,7 @@
mLockscreenShadeTransitionController.setQS(mQs);
mShadeTransitionController.setQs(mQs);
mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
- mQs.setScrollListener(mScrollListener);
+ mQs.setScrollListener(mQsScrollListener);
updateQsExpansion();
}
@@ -4204,7 +4068,7 @@
mQs = null;
}
}
- };
+ }
private void animateNextNotificationBounds(long duration, long delay) {
mAnimateNextNotificationBounds = true;
@@ -4294,13 +4158,7 @@
mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
}
- /**
- * TODO: this should be removed.
- * It's not correct to pass this view forward because other classes will end up adding
- * children to it. Theme will be out of sync.
- *
- * @return bottom area view
- */
+ //TODO(b/254875405): this should be removed.
public KeyguardBottomAreaView getKeyguardBottomAreaView() {
return mKeyguardBottomArea;
}
@@ -4329,11 +4187,8 @@
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- /**
- * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
- * security view of the bouncer.
- */
- public void onBouncerPreHideAnimation() {
+ /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */
+ public void startBouncerPreHideAnimation() {
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
mBarState,
@@ -4350,9 +4205,7 @@
}
}
- /**
- * Updates the views to the initial state for the fold to AOD animation
- */
+ /** Updates the views to the initial state for the fold to AOD animation. */
public void prepareFoldToAodAnimation() {
// Force show AOD UI even if we are not locked
showAodUi();
@@ -4394,14 +4247,11 @@
public void onAnimationEnd(Animator animation) {
endAction.run();
}
- }).setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- }).start();
+ }).setUpdateListener(anim -> mKeyguardStatusViewController.animateFoldToAod(
+ anim.getAnimatedFraction())).start();
}
- /**
- * Cancels fold to AOD transition and resets view state
- */
+ /** Cancels fold to AOD transition and resets view state. */
public void cancelFoldToAodAnimation() {
cancelAnimation();
resetAlpha();
@@ -4445,42 +4295,11 @@
}
}
- public boolean hasActiveClearableNotifications() {
- return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
- }
public RemoteInputController.Delegate createRemoteInputDelegate() {
return mNotificationStackScrollLayoutController.createDelegate();
}
- /**
- * Updates the notification views' sections and status bar icons. This is
- * triggered by the NotificationPresenter whenever there are changes to the underlying
- * notification data being displayed. In the new notification pipeline, this is handled in
- * {@link ShadeViewManager}.
- */
- public void updateNotificationViews() {
- mNotificationStackScrollLayoutController.updateFooter();
-
- mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
- }
-
- private List<ListEntry> createVisibleEntriesList() {
- List<ListEntry> entries = new ArrayList<>(
- mNotificationStackScrollLayoutController.getChildCount());
- for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
- View view = mNotificationStackScrollLayoutController.getChildAt(i);
- if (view instanceof ExpandableNotificationRow) {
- entries.add(((ExpandableNotificationRow) view).getEntry());
- }
- }
- return entries;
- }
-
- public void onUpdateRowStates() {
- mNotificationStackScrollLayoutController.onUpdateRowStates();
- }
-
public boolean hasPulsingNotifications() {
return mNotificationListContainer.hasPulsingNotifications();
}
@@ -4497,16 +4316,6 @@
mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
}
- private Runnable mHideExpandedRunnable;
- private final Runnable mMaybeHideExpandedRunnable = new Runnable() {
- @Override
- public void run() {
- if (getExpansionFraction() == 0.0f) {
- mView.post(mHideExpandedRunnable);
- }
- }
- };
-
/**
* Initialize objects instead of injecting to avoid circular dependencies.
*
@@ -4516,7 +4325,9 @@
CentralSurfaces centralSurfaces,
Runnable hideExpandedRunnable,
NotificationShelfController notificationShelfController) {
- setCentralSurfaces(centralSurfaces);
+ // TODO(b/254859580): this can be injected.
+ mCentralSurfaces = centralSurfaces;
+
mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
mNotificationShelfController = notificationShelfController;
@@ -4524,10 +4335,6 @@
updateMaxDisplayedNotifications(true);
}
- public void setAlpha(float alpha) {
- mView.setAlpha(alpha);
- }
-
public void resetTranslation() {
mView.setTranslationX(0f);
}
@@ -4546,22 +4353,18 @@
ViewGroupFadeHelper.reset(mView);
}
- public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
- public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
- public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
+ public ShadeHeadsUpChangedListener getOnHeadsUpChangedListener() {
return mOnHeadsUpChangedListener;
}
- public int getHeight() {
- return mView.getHeight();
- }
-
public void setHeaderDebugInfo(String text) {
if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
}
@@ -4570,10 +4373,6 @@
mConfigurationListener.onThemeChanged();
}
- private OnLayoutChangeListener createLayoutChangeListener() {
- return new OnLayoutChangeListener();
- }
-
@VisibleForTesting
TouchHandler createTouchHandler() {
return new TouchHandler();
@@ -4628,10 +4427,6 @@
}
};
- private OnConfigurationChangedListener createOnConfigurationChangedListener() {
- return new OnConfigurationChangedListener();
- }
-
public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
return mNotificationStackScrollLayoutController;
}
@@ -4672,13 +4467,7 @@
);
}
- private void unregisterSettingsChangeListener() {
- mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
- }
-
- /**
- * Updates notification panel-specific flags on {@link SysUiState}.
- */
+ /** Updates notification panel-specific flags on {@link SysUiState}. */
public void updateSystemUiStateFlags() {
if (SysUiState.DEBUG) {
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
@@ -4690,8 +4479,10 @@
.commitUpdate(mDisplayId);
}
- private void logf(String fmt, Object... args) {
- Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ private void debugLog(String fmt, Object... args) {
+ if (DEBUG_LOGCAT) {
+ Log.d(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ }
}
@VisibleForTesting
@@ -4755,9 +4546,8 @@
* Maybe vibrate as panel is opened.
*
* @param openingWithTouch Whether the panel is being opened with touch. If the panel is
- * instead
- * being opened programmatically (such as by the open panel gesture), we
- * always play haptic.
+ * instead being opened programmatically (such as by the open panel
+ * gesture), we always play haptic.
*/
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening) {
@@ -4856,8 +4646,8 @@
} else if (!mCentralSurfaces.isBouncerShowing()
&& !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick();
- onTrackingStopped(expands);
+ onEmptySpaceClick();
+ onTrackingStopped(true);
}
mVelocityTracker.clear();
}
@@ -4869,7 +4659,7 @@
private void endClosing() {
if (mClosing) {
- setIsClosing(false);
+ setClosing(false);
onClosingFinished();
}
}
@@ -4904,7 +4694,7 @@
boolean expandBecauseOfFalsing) {
float target = expand ? getMaxPanelHeight() : 0;
if (!expand) {
- setIsClosing(true);
+ setClosing(true);
}
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
@@ -4939,13 +4729,9 @@
animator.start();
}
- public String getName() {
- return mViewName;
- }
-
@VisibleForTesting
void setExpandedHeight(float height) {
- if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+ debugLog("setExpandedHeight(%.1f)", height);
setExpandedHeightInternal(height);
}
@@ -5037,7 +4823,7 @@
return mExpandedHeight;
}
- public float getExpandedFraction() {
+ private float getExpandedFraction() {
return mExpandedFraction;
}
@@ -5053,10 +4839,6 @@
return mClosing || mIsLaunchAnimationRunning;
}
- public boolean isFlinging() {
- return mIsFlinging;
- }
-
public boolean isTracking() {
return mTracking;
}
@@ -5203,8 +4985,7 @@
*/
public void updatePanelExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(),
- mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
@@ -5217,16 +4998,11 @@
&& !mIsSpringBackAnimation;
}
- /**
- * Gets called when the user performs a click anywhere in the empty area of the panel.
- *
- * @return whether the panel will be expanded after the action performed by this method
- */
- private boolean onEmptySpaceClick() {
- if (mHintAnimationRunning) {
- return true;
+ /** Called when the user performs a click anywhere in the empty area of the panel. */
+ private void onEmptySpaceClick() {
+ if (!mHintAnimationRunning) {
+ onMiddleClicked();
}
- return onMiddleClicked();
}
@VisibleForTesting
@@ -5243,7 +5019,7 @@
/** Returns the NotificationPanelView. */
public ViewGroup getView() {
- // TODO: remove this method, or at least reduce references to it.
+ // TODO(b/254878364): remove this method, or at least reduce references to it.
return mView;
}
@@ -5283,12 +5059,11 @@
return mShadeExpansionStateManager;
}
- private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
+ private final class NsslHeightChangedListener implements
+ ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
-
- // Block update if we are in quick settings and just the top padding changed
- // (i.e. view == null).
+ // Block update if we are in QS and just the top padding changed (i.e. view == null).
if (view == null && mQsExpanded) {
return;
}
@@ -5312,26 +5087,22 @@
}
@Override
- public void onReset(ExpandableView view) {
+ public void onReset(ExpandableView view) {}
+ }
+
+ private void collapseOrExpand() {
+ onQsExpansionStarted();
+ if (mQsExpanded) {
+ flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
+ true /* isClick */);
+ } else if (isQsExpansionEnabled()) {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+ flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
+ true /* isClick */);
}
}
- private class CollapseExpandAction implements Runnable {
- @Override
- public void run() {
- onQsExpansionStarted();
- if (mQsExpanded) {
- flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
- true /* isClick */);
- } else if (isQsExpansionEnabled()) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
- flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
- true /* isClick */);
- }
- }
- }
-
- private class OnOverscrollTopChangedListener implements
+ private final class NsslOverscrollTopChangedListener implements
NotificationStackScrollLayout.OnOverscrollTopChangedListener {
@Override
public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
@@ -5375,27 +5146,16 @@
}
}
- private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
- @Override
- public void onDynamicPrivacyChanged() {
- // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
- // of sync with the notification panel.
- if (mLinearDarkAmount != 0) {
- return;
- }
- mAnimateNextPositionUpdate = true;
+ private void onDynamicPrivacyChanged() {
+ // Do not request animation when pulsing or waking up, otherwise the clock will be out
+ // of sync with the notification panel.
+ if (mLinearDarkAmount != 0) {
+ return;
}
+ mAnimateNextPositionUpdate = true;
}
- private class OnEmptySpaceClickListener implements
- NotificationStackScrollLayout.OnEmptySpaceClickListener {
- @Override
- public void onEmptySpaceClicked(float x, float y) {
- onEmptySpaceClick();
- }
- }
-
- private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
+ private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
if (inPinnedMode) {
@@ -5435,32 +5195,31 @@
}
}
- private class HeightListener implements QS.HeightListener {
- public void onQsHeightChanged() {
- mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
- if (mQsExpanded && mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateExpandedHeightToMaxHeight();
- }
- if (mAccessibilityManager.isEnabled()) {
- mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
- mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
+ private void onQsHeightChanged() {
+ mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
+ if (mQsExpanded && mQsFullyExpanded) {
+ mQsExpansionHeight = mQsMaxExpansionHeight;
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ updateExpandedHeightToMaxHeight();
}
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
+ mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
}
- private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
+ private final class ConfigurationListener implements
+ ConfigurationController.ConfigurationListener {
@Override
public void onThemeChanged() {
- if (DEBUG_LOGCAT) Log.d(TAG, "onThemeChanged");
+ debugLog("onThemeChanged");
reInflateViews();
}
@Override
public void onSmallestScreenWidthChanged() {
Trace.beginSection("onSmallestScreenWidthChanged");
- if (DEBUG_LOGCAT) Log.d(TAG, "onSmallestScreenWidthChanged");
+ debugLog("onSmallestScreenWidthChanged");
// Can affect multi-user switcher visibility as it depends on screen size by default:
// it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
@@ -5477,27 +5236,26 @@
@Override
public void onDensityOrFontScaleChanged() {
- if (DEBUG_LOGCAT) Log.d(TAG, "onDensityOrFontScaleChanged");
+ debugLog("onDensityOrFontScaleChanged");
reInflateViews();
}
}
- private class SettingsChangeObserver extends ContentObserver {
-
+ private final class SettingsChangeObserver extends ContentObserver {
SettingsChangeObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onSettingsChanged");
+ debugLog("onSettingsChanged");
// Can affect multi-user switcher visibility
reInflateViews();
}
}
- private class StatusBarStateListener implements StateListener {
+ private final class StatusBarStateListener implements StateListener {
@Override
public void onStateChanged(int statusBarState) {
boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
@@ -5653,21 +5411,19 @@
setExpandedFraction(1f);
}
- /**
- * Sets the overstretch amount in raw pixels when dragging down.
- */
- public void setOverStrechAmount(float amount) {
+ /** Sets the overstretch amount in raw pixels when dragging down. */
+ public void setOverStretchAmount(float amount) {
float progress = amount / mView.getHeight();
- float overstretch = Interpolators.getOvershootInterpolation(progress);
- mOverStretchAmount = overstretch * mMaxOverscrollAmountForPulse;
+ float overStretch = Interpolators.getOvershootInterpolation(progress);
+ mOverStretchAmount = overStretch * mMaxOverscrollAmountForPulse;
positionClockAndNotifications(true /* forceUpdate */);
}
- private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
+ private final class ShadeAttachStateChangeListener implements View.OnAttachStateChangeListener {
@Override
public void onViewAttachedToWindow(View v) {
mFragmentService.getFragmentHostManager(mView)
- .addTagListener(QS.TAG, mFragmentListener);
+ .addTagListener(QS.TAG, mQsFragmentListener);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
mConfigurationController.addCallback(mConfigurationListener);
@@ -5682,16 +5438,16 @@
@Override
public void onViewDetachedFromWindow(View v) {
- unregisterSettingsChangeListener();
+ mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
mFragmentService.getFragmentHostManager(mView)
- .removeTagListener(QS.TAG, mFragmentListener);
+ .removeTagListener(QS.TAG, mQsFragmentListener);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mFalsingManager.removeTapListener(mFalsingTapListener);
}
}
- private final class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+ private final class ShadeLayoutChangeListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
@@ -5700,7 +5456,7 @@
mHasLayoutedSinceDown = true;
if (mUpdateFlingOnLayout) {
abortAnimations();
- fling(mUpdateFlingVelocity, true /* expands */);
+ fling(mUpdateFlingVelocity);
mUpdateFlingOnLayout = false;
}
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
@@ -5734,14 +5490,11 @@
updateExpandedHeight(getExpandedHeight());
updateHeader();
- // If we are running a size change animation, the animation takes care of the height of
- // the container. However, if we are not animating, we always need to make the QS
- // container
- // the desired height so when closing the QS detail, it stays smaller after the size
- // change
- // animation is finished but the detail view is still being animated away (this
- // animation
- // takes longer than the size change animation).
+ // If we are running a size change animation, the animation takes care of the height
+ // of the container. However, if we are not animating, we always need to make the QS
+ // container the desired height so when closing the QS detail, it stays smaller after
+ // the size change animation is finished but the detail view is still being animated
+ // away (this animation takes longer than the size change animation).
if (mQsSizeChangeAnimator == null && mQs != null) {
mQs.setHeightOverride(mQs.getDesiredHeight());
}
@@ -5767,13 +5520,12 @@
}
}
- private class DebugDrawable extends Drawable {
-
+ private final class DebugDrawable extends Drawable {
private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
private final Paint mDebugPaint = new Paint();
@Override
- public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
mDebugTextUsedYPositions.clear();
mDebugPaint.setColor(Color.RED);
@@ -5851,18 +5603,17 @@
}
}
- private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- // the same types of insets that are handled in NotificationShadeWindowView
- int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
- Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
- mDisplayTopInset = combinedInsets.top;
- mDisplayRightInset = combinedInsets.right;
+ @NonNull
+ private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) {
+ // the same types of insets that are handled in NotificationShadeWindowView
+ int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
+ Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
+ mDisplayTopInset = combinedInsets.top;
+ mDisplayRightInset = combinedInsets.right;
- mNavigationBarBottomHeight = insets.getStableInsetBottom();
- updateMaxHeadsUpTranslation();
- return insets;
- }
+ mNavigationBarBottomHeight = insets.getStableInsetBottom();
+ updateMaxHeadsUpTranslation();
+ return insets;
}
/** Removes any pending runnables that would collapse the panel. */
@@ -5870,9 +5621,6 @@
mView.removeCallbacks(mMaybeHideExpandedRunnable);
}
- @PanelState
- private int mCurrentPanelState = STATE_CLOSED;
-
private void onPanelStateChanged(@PanelState int state) {
updateQSExpansionEnabledAmbient();
@@ -5909,6 +5657,11 @@
}
@VisibleForTesting
+ StateListener getStatusBarStateListener() {
+ return mStatusBarStateListener;
+ }
+
+ @VisibleForTesting
boolean isHintAnimationRunning() {
return mHintAnimationRunning;
}
@@ -5956,7 +5709,7 @@
}
if (!isFullyCollapsed() && onQsIntercept(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
+ debugLog("onQsIntercept true");
return true;
}
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -6159,7 +5912,6 @@
*
* Flinging is also enabled in order to open or close the shade.
*/
-
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -6175,6 +5927,7 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mShadeLog.logMotionEvent(event, "onTouch: down action");
startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
mMinExpandHeight = 0.0f;
mPanelClosedOnDown = isFullyCollapsed();
@@ -6263,6 +6016,7 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mShadeLog.logMotionEvent(event, "onTouch: up/cancel action");
addMovement(event);
endMotionEvent(event, x, y, false /* forceCancel */);
// mHeightAnimator is null, there is no remaining frame, ends instrumenting.
@@ -6279,15 +6033,6 @@
}
}
- /** Listens for config changes. */
- public class OnConfigurationChangedListener implements
- NotificationPanelView.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- loadDimens();
- }
- }
-
static class SplitShadeTransitionAdapter extends Transition {
private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
@@ -6337,5 +6082,27 @@
return TRANSITION_PROPERTIES;
}
}
+
+ private final class ShadeAccessibilityDelegate extends AccessibilityDelegate {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action
+ == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
+ || action
+ == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
+ mStatusBarKeyguardViewManager.showBouncer(true);
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 2b788d8..7f1bba3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -77,4 +77,50 @@
}
)
}
+
+ fun logExpansionChanged(
+ message: String,
+ fraction: Float,
+ expanded: Boolean,
+ tracking: Boolean,
+ dragDownPxAmount: Float,
+ ) {
+ log(LogLevel.VERBOSE, {
+ str1 = message
+ double1 = fraction.toDouble()
+ bool1 = expanded
+ bool2 = tracking
+ long1 = dragDownPxAmount.toLong()
+ }, {
+ "$str1 fraction=$double1,expanded=$bool1," +
+ "tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount"
+ })
+ }
+
+ fun logQsExpansionChanged(
+ message: String,
+ qsExpanded: Boolean,
+ qsMinExpansionHeight: Int,
+ qsMaxExpansionHeight: Int,
+ stackScrollerOverscrolling: Boolean,
+ dozing: Boolean,
+ qsAnimatorExpand: Boolean,
+ animatingQs: Boolean
+ ) {
+ log(LogLevel.VERBOSE, {
+ str1 = message
+ bool1 = qsExpanded
+ int1 = qsMinExpansionHeight
+ int2 = qsMaxExpansionHeight
+ bool2 = stackScrollerOverscrolling
+ bool3 = dozing
+ bool4 = qsAnimatorExpand
+ // 0 = false, 1 = true
+ long1 = animatingQs.compareTo(false).toLong()
+ }, {
+ "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," +
+ "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," +
+ "animatingQs=$long1"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index a2e4536..b8302d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -663,7 +663,7 @@
} else {
pulseHeight = height
val overflow = nsslController.setPulseHeight(height)
- notificationPanelController.setOverStrechAmount(overflow)
+ notificationPanelController.setOverStretchAmount(overflow)
val transitionHeight = if (keyguardBypassController.bypassEnabled) height else 0.0f
transitionToShadeAmountCommon(transitionHeight)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8a31ed9..470cbcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -198,6 +198,13 @@
// At this point we just need to initiate the transfer
val summaryUpdate = mPostedEntries[logicalSummary.key]
+ // Because we now know for certain that some child is going to alert for this summary
+ // (as we have found a child to transfer the alert to), mark the group as having
+ // interrupted. This will allow us to know in the future that the "should heads up"
+ // state of this group has already been handled, just not via the summary entry itself.
+ logicalSummary.setInterruption()
+ mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentAlert.key)
+
// If the summary was not attached, then remove the alert from the detached summary.
// Otherwise we can simply ignore its posted update.
if (!isSummaryAttached) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index dfaa291..473c35d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -69,4 +69,13 @@
"updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1"
})
}
+
+ fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = summaryKey
+ str2 = childKey
+ }, {
+ "marked group summary as interrupted: $str1 for alert transfer to child: $str2"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index d227ed3..eb7a742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3468,10 +3468,7 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- /**
- * TODO: Remove this method. Views should not be passed forward. Will cause theme issues.
- * @return bottom area view
- */
+ //TODO(b/254875405): this should be removed.
@Override
public KeyguardBottomAreaView getKeyguardBottomAreaView() {
return mNotificationPanelViewController.getKeyguardBottomAreaView();
@@ -4184,7 +4181,7 @@
*/
@Override
public void onBouncerPreHideAnimation() {
- mNotificationPanelViewController.onBouncerPreHideAnimation();
+ mNotificationPanelViewController.startBouncerPreHideAnimation();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index bc2ae64..e326611 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -67,7 +67,7 @@
internal const val QS_DEFAULT_POSITION = 7
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
- internal const val PREFS_CONTROLS_FILE = "controls_prefs"
+ const val PREFS_CONTROLS_FILE = "controls_prefs"
internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
private const val SEEDING_MAX = 2
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 10a09dd1..61eadeb 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -44,6 +44,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
@@ -89,6 +90,7 @@
includes = {
AospPolicyModule.class,
GestureModule.class,
+ MultiUserUtilsModule.class,
PowerModule.class,
QSModule.class,
ReferenceScreenshotModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f9bec65..52f8ef8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -55,6 +55,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.SessionTracker;
@@ -143,6 +144,8 @@
private SidefpsController mSidefpsController;
@Mock
private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
+ @Mock
+ private FalsingA11yDelegate mFalsingA11yDelegate;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@@ -186,7 +189,8 @@
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
- mSessionTracker, Optional.of(mSidefpsController)).create(mSecurityCallback);
+ mSessionTracker, Optional.of(mSidefpsController), mFalsingA11yDelegate).create(
+ mSecurityCallback);
}
@Test
@@ -225,7 +229,8 @@
mKeyguardSecurityContainerController.updateResources();
verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
// Update rotation. Should trigger update
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
@@ -233,7 +238,8 @@
mKeyguardSecurityContainerController.updateResources();
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
private void touchDown() {
@@ -269,7 +275,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
@Test
@@ -282,7 +289,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
@Test
@@ -293,7 +301,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
@Test
@@ -307,7 +316,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
any(UserSwitcherController.class),
- captor.capture());
+ captor.capture(),
+ eq(mFalsingA11yDelegate));
captor.getValue().showUnlockToContinueMessage();
verify(mKeyguardPasswordViewControllerMock).showMessage(
getContext().getString(R.string.keyguard_unlock_to_continue), null);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 82d3ca7..1bd14e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -31,6 +31,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
+import static com.android.keyguard.KeyguardSecurityContainer.MODE_USER_SWITCHER;
import static com.google.common.truth.Truth.assertThat;
@@ -54,6 +55,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
@@ -87,6 +89,8 @@
private FalsingManager mFalsingManager;
@Mock
private UserSwitcherController mUserSwitcherController;
+ @Mock
+ private FalsingA11yDelegate mFalsingA11yDelegate;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@@ -111,15 +115,14 @@
when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
}
+
@Test
public void testOnApplyWindowInsets() {
int paddingBottom = getContext().getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
int imeInsetAmount = paddingBottom + 1;
int systemBarInsetAmount = 0;
-
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(MODE_DEFAULT);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -140,8 +143,7 @@
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
int systemBarInsetAmount = paddingBottom + 1;
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(MODE_DEFAULT);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -157,11 +159,8 @@
@Test
public void testDefaultViewMode() {
- mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {
- });
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(MODE_ONE_HANDED);
+ initMode(MODE_DEFAULT);
ConstraintSet.Constraint viewFlipperConstraint =
getViewConstraint(mSecurityViewFlipper.getId());
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
@@ -377,8 +376,7 @@
private void setupUserSwitcher() {
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
- mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {});
+ initMode(MODE_USER_SWITCHER);
}
private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -396,8 +394,7 @@
private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
- mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(mode);
}
/** Get the ConstraintLayout constraint of the view. */
@@ -406,4 +403,10 @@
constraintSet.clone(mKeyguardSecurityContainer);
return constraintSet.getConstraint(viewId);
}
+
+ private void initMode(int mode) {
+ mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController, () -> {
+ }, mFalsingA11yDelegate);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 9481349..b811aab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -96,7 +96,6 @@
assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
-
@Test
public void testA11yDisablesTap() {
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
@@ -159,4 +158,11 @@
});
assertThat(mBrightLineFalsingManager.isProximityNear()).isFalse();
}
+
+ @Test
+ public void testA11yAction() {
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
+ when(mFalsingDataProvider.isA11yAction()).thenReturn(true);
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
new file mode 100644
index 0000000..2c904e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FalsingA11yDelegateTest : SysuiTestCase() {
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var view: View
+ lateinit var underTest: FalsingA11yDelegate
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = FalsingA11yDelegate(falsingCollector)
+ }
+
+ @Test
+ fun testPerformAccessibilityAction_ACTION_CLICK() {
+ underTest.performAccessibilityAction(view, ACTION_CLICK, null)
+ verify(falsingCollector).onA11yAction()
+ }
+
+ @Test
+ fun testPerformAccessibilityAction_not_ACTION_CLICK() {
+ underTest.performAccessibilityAction(view, ACTION_LONG_CLICK, null)
+ verify(falsingCollector, never()).onA11yAction()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index fa9c41a..442bf91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -267,4 +267,10 @@
mFalsingCollector.onTouchEvent(up);
verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class));
}
+
+ @Test
+ public void testOnA11yAction() {
+ mFalsingCollector.onA11yAction();
+ verify(mFalsingDataProvider).onA11yAction();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 5dc607f..d315c2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -310,4 +310,10 @@
// an empty array.
assertThat(mDataProvider.getPriorMotionEvents()).isNotNull();
}
+
+ @Test
+ public void test_MotionEventComplete_A11yAction() {
+ mDataProvider.onA11yAction();
+ assertThat(mDataProvider.isA11yAction()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
new file mode 100644
index 0000000..49c7442
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsUiControllerImplTest : SysuiTestCase() {
+ @Mock lateinit var controlsController: ControlsController
+ @Mock lateinit var controlsListingController: ControlsListingController
+ @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
+ @Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var shadeController: ShadeController
+ @Mock lateinit var iconCache: CustomIconCache
+ @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
+ @Mock lateinit var keyguardStateController: KeyguardStateController
+ @Mock lateinit var userFileManager: UserFileManager
+ @Mock lateinit var userTracker: UserTracker
+ val sharedPreferences = FakeSharedPreferences()
+
+ var uiExecutor = FakeExecutor(FakeSystemClock())
+ var bgExecutor = FakeExecutor(FakeSystemClock())
+ lateinit var underTest: ControlsUiControllerImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ ControlsUiControllerImpl(
+ Lazy { controlsController },
+ context,
+ uiExecutor,
+ bgExecutor,
+ Lazy { controlsListingController },
+ controlActionCoordinator,
+ activityStarter,
+ shadeController,
+ iconCache,
+ controlsMetricsLogger,
+ keyguardStateController,
+ userFileManager,
+ userTracker
+ )
+ `when`(
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ 0,
+ 0
+ )
+ )
+ .thenReturn(sharedPreferences)
+ `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+ .thenReturn(sharedPreferences)
+ `when`(userTracker.userId).thenReturn(0)
+ }
+
+ @Test
+ fun testGetPreferredStructure() {
+ val structureInfo = mock(StructureInfo::class.java)
+ underTest.getPreferredStructure(listOf(structureInfo))
+ verify(userFileManager, times(2))
+ .getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = 0,
+ userId = 0
+ )
+ }
+
+ @Test
+ fun testGetPreferredStructure_differentUserId() {
+ val structureInfo =
+ listOf(
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()),
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()),
+ )
+ sharedPreferences
+ .edit()
+ .putString("controls_component", structureInfo[0].componentName.flattenToString())
+ .putString("controls_structure", structureInfo[0].structure.toString())
+ .commit()
+
+ val differentSharedPreferences = FakeSharedPreferences()
+ differentSharedPreferences
+ .edit()
+ .putString("controls_component", structureInfo[1].componentName.flattenToString())
+ .putString("controls_structure", structureInfo[1].structure.toString())
+ .commit()
+
+ val previousPreferredStructure = underTest.getPreferredStructure(structureInfo)
+
+ `when`(
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ 0,
+ 1
+ )
+ )
+ .thenReturn(differentSharedPreferences)
+ `when`(userTracker.userId).thenReturn(1)
+
+ val currentPreferredStructure = underTest.getPreferredStructure(structureInfo)
+
+ assertThat(previousPreferredStructure).isEqualTo(structureInfo[0])
+ assertThat(currentPreferredStructure).isEqualTo(structureInfo[1])
+ assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 93a1243..45b4353 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -129,7 +129,6 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -153,7 +152,6 @@
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -199,7 +197,6 @@
@Mock private KeyguardBottomAreaView mKeyguardBottomArea;
@Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
@Mock private KeyguardBottomAreaView mQsFrame;
- @Mock private NotificationIconAreaController mNotificationAreaController;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationShelfController mNotificationShelfController;
@Mock private KeyguardStatusBarView mKeyguardStatusBar;
@@ -227,7 +224,7 @@
@Mock private Resources mResources;
@Mock private Configuration mConfiguration;
@Mock private KeyguardClockSwitch mKeyguardClockSwitch;
- @Mock private MediaHierarchyManager mMediaHiearchyManager;
+ @Mock private MediaHierarchyManager mMediaHierarchyManager;
@Mock private ConversationNotificationManager mConversationNotificationManager;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@@ -254,7 +251,6 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockIconViewController mLockIconViewController;
@Mock private KeyguardMediaController mKeyguardMediaController;
- @Mock private PrivacyDotViewController mPrivacyDotViewController;
@Mock private NavigationModeController mNavigationModeController;
@Mock private NavigationBarController mNavigationBarController;
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
@@ -294,7 +290,7 @@
private ConfigurationController mConfigurationController;
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
- private View.AccessibilityDelegate mAccessibiltyDelegate;
+ private View.AccessibilityDelegate mAccessibilityDelegate;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
private Handler mMainHandler;
@@ -456,7 +452,7 @@
mShadeLog,
mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
- mConversationNotificationManager, mMediaHiearchyManager,
+ mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
@@ -465,7 +461,6 @@
mKeyguardUserSwitcherComponentFactory,
mKeyguardStatusBarViewComponentFactory,
mLockscreenShadeTransitionController,
- mNotificationAreaController,
mAuthController,
mScrimController,
mUserManager,
@@ -474,7 +469,6 @@
mAmbientState,
mLockIconViewController,
mKeyguardMediaController,
- mPrivacyDotViewController,
mTapAgainViewController,
mNavigationModeController,
mNavigationBarController,
@@ -516,9 +510,9 @@
ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
- mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
+ mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue();
mNotificationPanelViewController.getStatusBarStateController()
- .addCallback(mNotificationPanelViewController.mStatusBarStateListener);
+ .addCallback(mNotificationPanelViewController.getStatusBarStateListener());
mNotificationPanelViewController
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
verify(mNotificationStackScrollLayoutController)
@@ -773,8 +767,8 @@
0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
0 /* metaState */));
- assertThat(mNotificationPanelViewController.getClosing()).isTrue();
- assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+ assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
// simulate touch that does not exceed touch slop
onTouchEvent(MotionEvent.obtain(2L /* downTime */,
@@ -788,8 +782,8 @@
0 /* metaState */));
// fling should still be called after a touch that does not exceed touch slop
- assertThat(mNotificationPanelViewController.getClosing()).isTrue();
- assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+ assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
}
@Test
@@ -844,7 +838,7 @@
@Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
- mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
+ mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
assertThat(actionList).containsAtLeastElementsIn(
@@ -856,7 +850,7 @@
@Test
public void testA11y_scrollForward() {
- mAccessibiltyDelegate.performAccessibilityAction(
+ mAccessibilityDelegate.performAccessibilityAction(
mView,
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
null);
@@ -866,7 +860,7 @@
@Test
public void testA11y_scrollUp() {
- mAccessibiltyDelegate.performAccessibilityAction(
+ mAccessibilityDelegate.performAccessibilityAction(
mView,
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
null);
@@ -1329,11 +1323,11 @@
public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
enableSplitShade(/* enabled= */ true);
mShadeExpansionStateManager.updateState(STATE_CLOSED);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
mShadeExpansionStateManager.updateState(STATE_OPENING);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isTrue();
}
@Test
@@ -1345,18 +1339,18 @@
// going to lockscreen would trigger STATE_OPENING
mShadeExpansionStateManager.updateState(STATE_OPENING);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
}
@Test
public void testQsImmediateResetsWhenPanelOpensOrCloses() {
- mNotificationPanelViewController.mQsExpandImmediate = true;
+ mNotificationPanelViewController.setQsExpandImmediate(true);
mShadeExpansionStateManager.updateState(STATE_OPEN);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
- mNotificationPanelViewController.mQsExpandImmediate = true;
+ mNotificationPanelViewController.setQsExpandImmediate(true);
mShadeExpansionStateManager.updateState(STATE_CLOSED);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
}
@Test
@@ -1399,7 +1393,7 @@
@Test
public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mQsFrame.getX()).thenReturn(0f);
when(mQsFrame.getWidth()).thenReturn(1000);
when(mQsHeader.getTop()).thenReturn(0);
@@ -1419,7 +1413,7 @@
@Test
public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() {
enableSplitShade(true);
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mQsFrame.getX()).thenReturn(0f);
when(mQsFrame.getWidth()).thenReturn(1000);
when(mQsHeader.getTop()).thenReturn(0);
@@ -1495,7 +1489,7 @@
@Test
public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
setIsFullWidth(true);
@@ -1504,7 +1498,7 @@
@Test
public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
setIsFullWidth(false);
@@ -1513,7 +1507,7 @@
@Test
public void onLayoutChange_qsNotSet_doesNotCrash() {
- mNotificationPanelViewController.mQs = null;
+ mNotificationPanelViewController.setQs(null);
triggerLayoutChange();
}
@@ -1539,7 +1533,7 @@
@Test
public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() {
float squishinessFraction = 0.456f;
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
.thenReturn(squishinessFraction);
when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
@@ -1567,7 +1561,7 @@
public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() {
float lsSquishinessFraction = 0.456f;
float nsslSquishinessFraction = 0.987f;
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
.thenReturn(lsSquishinessFraction);
when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
@@ -1586,7 +1580,7 @@
@Test
public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
@@ -1601,7 +1595,7 @@
@Test
public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
@@ -1616,7 +1610,7 @@
@Test
public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
@@ -1631,7 +1625,7 @@
@Test
public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(true, false);
@@ -1645,7 +1639,7 @@
@Test
public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(SHADE_LOCKED);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
@@ -1664,11 +1658,11 @@
public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
// Given: Shade is expanded
mNotificationPanelViewController.notifyExpandingFinished();
- mNotificationPanelViewController.setIsClosing(false);
+ mNotificationPanelViewController.setClosing(false);
// When: Shade flings to close not canceled
mNotificationPanelViewController.notifyExpandingStarted();
- mNotificationPanelViewController.setIsClosing(true);
+ mNotificationPanelViewController.setClosing(true);
mNotificationPanelViewController.onFlingEnd(false);
// Then: AmbientState's mIsClosing should be set to false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 3ff7639..f96c39f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -406,6 +406,10 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupSibling1)
+
+ // In addition make sure we have explicitly marked the summary as having interrupted due
+ // to the alert being transferred
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -424,6 +428,7 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupChild1)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -449,6 +454,7 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -474,6 +480,7 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupChild1)
verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -512,6 +519,7 @@
verify(mHeadsUpManager).showNotification(mGroupPriority)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -548,6 +556,7 @@
verify(mHeadsUpManager).showNotification(mGroupPriority)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -582,6 +591,7 @@
verify(mHeadsUpManager).showNotification(mGroupPriority)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -672,6 +682,35 @@
}
@Test
+ fun testNoTransfer_groupSummaryNotAlerting() {
+ // When we have a group where the summary should not alert and exactly one child should
+ // alert, we should never mark the group summary as interrupted (because it doesn't).
+ setShouldHeadsUp(mGroupSummary, false)
+ setShouldHeadsUp(mGroupChild1, true)
+ setShouldHeadsUp(mGroupChild2, false)
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mCollectionListener.onEntryAdded(mGroupChild2)
+ val groupEntry = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupChild1, mGroupChild2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupChild1)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupChild1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ assertFalse(mGroupSummary.hasInterrupted())
+ }
+
+ @Test
fun testOnRankingApplied_newEntryShouldAlert() {
// GIVEN that mEntry has never interrupted in the past, and now should
// and is new enough to do so
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 47b4156..e3ae03c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3651,6 +3651,10 @@
throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
+ " not tracked by accessibility.");
}
+ if (mProxyManager.isProxyed(displayId)) {
+ throw new IllegalArgumentException("The display " + displayId + " is already being"
+ + "proxy-ed");
+ }
mProxyManager.registerProxy(client, displayId);
return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 934b665..247f320 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -32,6 +32,7 @@
import android.os.IBinder;
import android.os.RemoteCallback;
import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityDisplayProxy;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.Nullable;
@@ -44,7 +45,7 @@
import java.util.Set;
/**
- * Represents the system connection to an {@link android.view.accessibility.AccessibilityProxy}.
+ * Represents the system connection to an {@link AccessibilityDisplayProxy}.
*
* <p>Most methods are no-ops since this connection does not need to capture input or listen to
* hardware-related changes.
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index fb0b8f3..a2ce610 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
import android.accessibilityservice.IAccessibilityServiceClient;
+import java.util.HashSet;
+
/**
* Manages proxy connections.
*
@@ -26,6 +28,7 @@
*/
public class ProxyManager {
private final Object mLock;
+ private final HashSet<Integer> mDisplayIds = new HashSet<>();
ProxyManager(Object lock) {
mLock = lock;
@@ -35,12 +38,21 @@
* TODO: Create the proxy service connection.
*/
public void registerProxy(IAccessibilityServiceClient client, int displayId) {
+ mDisplayIds.add(displayId);
}
/**
* TODO: Unregister the proxy service connection based on display id.
*/
public boolean unregisterProxy(int displayId) {
+ mDisplayIds.remove(displayId);
return true;
}
+
+ /**
+ * Checks if a display id is being proxy-ed.
+ */
+ public boolean isProxyed(int displayId) {
+ return mDisplayIds.contains(displayId);
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 47ce592..64b7688 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -24,6 +24,7 @@
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
@@ -416,6 +417,14 @@
@GuardedBy("mLock")
private boolean mPreviouslyFillDialogPotentiallyStarted;
+ /**
+ * Keeps the fill dialog trigger ids of the last response. This invalidates
+ * the trigger ids of the previous response.
+ */
+ @Nullable
+ @GuardedBy("mLock")
+ private AutofillId[] mLastFillDialogTriggerIds;
+
void onSwitchInputMethodLocked() {
// One caveat is that for the case where the focus is on a field for which regular autofill
// returns null, and augmented autofill is triggered, and then the user switches the input
@@ -1222,6 +1231,8 @@
return;
}
+ mLastFillDialogTriggerIds = response.getFillDialogTriggerIds();
+
final int flags = response.getFlags();
if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
Slog.v(TAG, "Service requested to wait for delayed fill response.");
@@ -1310,6 +1321,7 @@
// fallback to the default platform password manager
mSessionFlags.mClientSuggestionsEnabled = false;
+ mLastFillDialogTriggerIds = null;
final InlineSuggestionsRequest inlineRequest =
(mLastInlineSuggestionsRequest != null
@@ -1348,6 +1360,7 @@
+ (timedOut ? "timeout" : "failure"));
}
mService.resetLastResponse();
+ mLastFillDialogTriggerIds = null;
final LogMaker requestLog = mRequestLogs.get(requestId);
if (requestLog == null) {
Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId);
@@ -3049,6 +3062,11 @@
}
}
+ if ((flags & FLAG_RESET_FILL_DIALOG_STATE) != 0) {
+ if (sDebug) Log.d(TAG, "force to reset fill dialog state");
+ mSessionFlags.mFillDialogDisabled = false;
+ }
+
switch(action) {
case ACTION_START_SESSION:
// View is triggering autofill.
@@ -3488,10 +3506,8 @@
}
private boolean isFillDialogUiEnabled() {
- // TODO read from Settings or somewhere
- final boolean isSettingsEnabledFillDialog = true;
synchronized (mLock) {
- return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
+ return !mSessionFlags.mFillDialogDisabled;
}
}
@@ -3517,14 +3533,25 @@
AutofillId filledId, String filterText, int flags) {
if (!isFillDialogUiEnabled()) {
// Unsupported fill dialog UI
+ if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled");
return false;
}
if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
// IME is showing, fallback to normal suggestions UI
+ if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing");
return false;
}
+ synchronized (mLock) {
+ if (mLastFillDialogTriggerIds == null
+ || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) {
+ // Last fill dialog triggered ids are changed.
+ if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
+ return false;
+ }
+ }
+
final Drawable serviceIcon = getServiceIcon();
getUiForShowing().showFillDialog(filledId, response, filterText,
@@ -4394,6 +4421,13 @@
if (mSessionFlags.mAugmentedAutofillOnly) {
pw.print(prefix); pw.println("For Augmented Autofill Only");
}
+ if (mSessionFlags.mFillDialogDisabled) {
+ pw.print(prefix); pw.println("Fill Dialog disabled");
+ }
+ if (mLastFillDialogTriggerIds != null) {
+ pw.print(prefix); pw.println("Last Fill Dialog trigger ids: ");
+ pw.println(mSelectedDatasetIds);
+ }
if (mAugmentedAutofillDestroyer != null) {
pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2eaddb1..062afe9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13874,6 +13874,29 @@
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ final int cookie = BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+ final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+ intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras,
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
+ ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
+ allowBackgroundActivityStarts, backgroundActivityStartsToken, broadcastAllowList,
+ filterExtrasForReceiver);
+ BroadcastQueue.traceEnd(cookie);
+ return res;
+ }
+
+ @GuardedBy("this")
+ final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+ @Nullable String callerFeatureId, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+ Bundle resultExtras, String[] requiredPermissions,
+ String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+ boolean ordered, boolean sticky, int callingPid, int callingUid,
+ int realCallingUid, int realCallingPid, int userId,
+ boolean allowBackgroundActivityStarts,
+ @Nullable IBinder backgroundActivityStartsToken,
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
// Ensure all internal loopers are registered for idle checks
BroadcastLoopers.addMyLooper();
@@ -14425,6 +14448,7 @@
}
// Figure out who all will receive this broadcast.
+ final int cookie = BroadcastQueue.traceBegin("queryReceivers");
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
// Need to resolve the intent to interested receivers...
@@ -14455,6 +14479,7 @@
resolvedType, false /*defaultOnly*/, userId);
}
}
+ BroadcastQueue.traceEnd(cookie);
final boolean replacePending =
(intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index bebb484..b828720 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -25,6 +25,8 @@
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -37,6 +39,7 @@
public class BroadcastLoopers {
private static final String TAG = "BroadcastLoopers";
+ @GuardedBy("sLoopers")
private static final ArraySet<Looper> sLoopers = new ArraySet<>();
/**
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index f7d24e9..2e12309 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -114,7 +114,14 @@
* dispatched to this process, in the same representation as
* {@link #mPending}.
*/
- private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>();
+ private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>(4);
+
+ /**
+ * Ordered collection of "offload" broadcasts that are waiting to be
+ * dispatched to this process, in the same representation as
+ * {@link #mPending}.
+ */
+ private final ArrayDeque<SomeArgs> mPendingOffload = new ArrayDeque<>(4);
/**
* Broadcast actively being dispatched to this process.
@@ -148,8 +155,7 @@
private boolean mActiveViaColdStart;
/**
- * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of
- * these various flavors.
+ * Count of pending broadcasts of these various flavors.
*/
private int mCountForeground;
private int mCountOrdered;
@@ -177,6 +183,16 @@
this.uid = uid;
}
+ private @NonNull ArrayDeque<SomeArgs> getQueueForBroadcast(@NonNull BroadcastRecord record) {
+ if (record.isUrgent()) {
+ return mPendingUrgent;
+ } else if (record.isOffload()) {
+ return mPendingOffload;
+ } else {
+ return mPending;
+ }
+ }
+
/**
* Enqueue the given broadcast to be dispatched to this process at some
* future point in time. The target receiver is indicated by the given index
@@ -193,10 +209,12 @@
public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
int blockedUntilTerminalCount) {
if (record.isReplacePending()) {
- boolean didReplace = replaceBroadcastInQueue(mPending,
- record, recordIndex, blockedUntilTerminalCount)
- || replaceBroadcastInQueue(mPendingUrgent,
- record, recordIndex, blockedUntilTerminalCount);
+ boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex,
+ blockedUntilTerminalCount)
+ || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex,
+ blockedUntilTerminalCount)
+ || replaceBroadcastInQueue(mPendingOffload, record, recordIndex,
+ blockedUntilTerminalCount);
if (didReplace) {
return;
}
@@ -213,8 +231,7 @@
// issued ahead of others that are already pending, for example if this new
// broadcast is in a different delivery class or is tied to a direct user interaction
// with implicit responsiveness expectations.
- final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending;
- queue.addLast(newBroadcastArgs);
+ getQueueForBroadcast(record).addLast(newBroadcastArgs);
onBroadcastEnqueued(record, recordIndex);
}
@@ -227,7 +244,7 @@
* {@code false} otherwise.
*/
private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
- @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+ @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
while (it.hasNext()) {
@@ -279,10 +296,13 @@
*/
public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
@NonNull BroadcastConsumer consumer, boolean andRemove) {
- boolean didSomething = forEachMatchingBroadcastInQueue(mPending,
+ boolean didSomething = false;
+ didSomething |= forEachMatchingBroadcastInQueue(mPending,
predicate, consumer, andRemove);
didSomething |= forEachMatchingBroadcastInQueue(mPendingUrgent,
predicate, consumer, andRemove);
+ didSomething |= forEachMatchingBroadcastInQueue(mPendingOffload,
+ predicate, consumer, andRemove);
return didSomething;
}
@@ -516,7 +536,7 @@
}
public boolean isEmpty() {
- return mPending.isEmpty() && mPendingUrgent.isEmpty();
+ return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty();
}
public boolean isActive() {
@@ -537,6 +557,8 @@
return mPendingUrgent;
} else if (!mPending.isEmpty()) {
return mPending;
+ } else if (!mPendingOffload.isEmpty()) {
+ return mPendingOffload;
}
return null;
}
@@ -581,12 +603,15 @@
}
final SomeArgs next = mPending.peekFirst();
final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
+ final SomeArgs nextOffload = mPendingOffload.peekFirst();
// Empty queue is past any barrier
- final boolean nextLater = next == null
+ final boolean nextLater = (next == null)
|| ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
- final boolean nextUrgentLater = nextUrgent == null
+ final boolean nextUrgentLater = (nextUrgent == null)
|| ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
- return nextLater && nextUrgentLater;
+ final boolean nextOffloadLater = (nextOffload == null)
+ || ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime;
+ return nextLater && nextUrgentLater && nextOffloadLater;
}
public boolean isRunnable() {
@@ -726,8 +751,9 @@
// If we have too many broadcasts pending, bypass any delays that
// might have been applied above to aid draining
- if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) {
- mRunnableAt = runnableAt;
+ if (mPending.size() + mPendingUrgent.size()
+ + mPendingOffload.size() >= constants.MAX_PENDING_BROADCASTS) {
+ mRunnableAt = Math.min(mRunnableAt, runnableAt);
mRunnableAtReason = REASON_MAX_PENDING;
}
} else {
@@ -845,23 +871,28 @@
pw.println();
pw.increaseIndent();
if (mActive != null) {
- dumpRecord(now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
+ dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
}
for (SomeArgs args : mPendingUrgent) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord(now, pw, r, args.argi1, args.argi2);
+ dumpRecord("URGENT", now, pw, r, args.argi1, args.argi2);
}
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord(now, pw, r, args.argi1, args.argi2);
+ dumpRecord(null, now, pw, r, args.argi1, args.argi2);
+ }
+ for (SomeArgs args : mPendingOffload) {
+ final BroadcastRecord r = (BroadcastRecord) args.arg1;
+ dumpRecord("OFFLOAD", now, pw, r, args.argi1, args.argi2);
}
pw.decreaseIndent();
pw.println();
}
@NeverCompile
- private void dumpRecord(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw,
- @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+ private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
+ @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex,
+ int blockedUntilTerminalCount) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
pw.print(' ');
pw.println(record.toShortString());
@@ -872,6 +903,10 @@
pw.print(" at ");
TimeUtils.formatDuration(record.scheduledTime[recordIndex], now, pw);
}
+ if (flavor != null) {
+ pw.print(' ');
+ pw.print(flavor);
+ }
final Object receiver = record.receivers.get(recordIndex);
if (receiver instanceof BroadcastFilter) {
final BroadcastFilter filter = (BroadcastFilter) receiver;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 1e172fc..e0fab2c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.Handler;
+import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -76,6 +77,18 @@
}
}
+ static int traceBegin(@NonNull String methodName) {
+ final int cookie = methodName.hashCode();
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ TAG, methodName, cookie);
+ return cookie;
+ }
+
+ static void traceEnd(int cookie) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ TAG, cookie);
+ }
+
@Override
public String toString() {
return mQueueName;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5750619..af2a97e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -64,7 +64,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.Trace;
import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.IndentingPrintWriter;
@@ -144,14 +143,6 @@
mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
}
- // TODO: add support for replacing pending broadcasts
- // TODO: add support for merging pending broadcasts
-
- // TODO: consider reordering foreground broadcasts within queue
-
- // TODO: pause queues when background services are running
- // TODO: pause queues when processes are frozen
-
/**
* Map from UID to per-process broadcast queues. If a UID hosts more than
* one process, each additional process is stored as a linked list using
@@ -222,12 +213,22 @@
private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
private static final int MSG_CHECK_HEALTH = 5;
+ private static final int MSG_FINISH_RECEIVER = 6;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
}
+ private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+ @DeliveryState int deliveryState, @NonNull String reason) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = queue;
+ args.argi1 = deliveryState;
+ args.arg2 = reason;
+ mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
+ }
+
private final Handler mLocalHandler;
private final Handler.Callback mLocalCallback = (msg) -> {
@@ -266,6 +267,17 @@
}
return true;
}
+ case MSG_FINISH_RECEIVER: {
+ synchronized (mService) {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
+ final int deliveryState = args.argi1;
+ final String reason = (String) args.arg2;
+ args.recycle();
+ finishReceiverLocked(queue, deliveryState, reason);
+ }
+ return true;
+ }
}
return false;
};
@@ -309,6 +321,7 @@
return;
}
+ final int cookie = traceBegin("updateRunnableList");
final boolean wantQueue = queue.isRunnable();
final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
|| (queue.runnableAtNext != null);
@@ -335,6 +348,8 @@
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
+
+ traceEnd(cookie);
}
/**
@@ -349,7 +364,7 @@
int avail = mRunning.length - getRunningSize();
if (avail == 0) return;
- final int cookie = traceBegin(TAG, "updateRunningList");
+ final int cookie = traceBegin("updateRunningList");
final long now = SystemClock.uptimeMillis();
// If someone is waiting for a state, everything is runnable now
@@ -449,7 +464,7 @@
});
}
- traceEnd(TAG, cookie);
+ traceEnd(cookie);
}
@Override
@@ -516,7 +531,8 @@
if (queue != null) {
// If queue was running a broadcast, fail it
if (queue.isActive()) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "onApplicationCleanupLocked");
}
// Skip any pending registered receivers, since the old process
@@ -544,6 +560,7 @@
public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
if (DEBUG_BROADCAST) logv("Enqueuing " + r + " for " + r.receivers.size() + " receivers");
+ final int cookie = traceBegin("enqueueBroadcast");
r.applySingletonPolicy(mService);
final IntentFilter removeMatchingFilter = (r.options != null)
@@ -613,6 +630,8 @@
if (r.receivers.isEmpty()) {
scheduleResultTo(r);
}
+
+ traceEnd(cookie);
}
private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
@@ -668,7 +687,8 @@
// Ignore registered receivers from a previous PID
if (receiver instanceof BroadcastFilter) {
mRunningColdStart = null;
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
+ "BroadcastFilter for cold app");
return;
}
@@ -690,7 +710,8 @@
hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
if (queue.app == null) {
mRunningColdStart = null;
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "startProcessLocked failed");
return;
}
}
@@ -721,33 +742,37 @@
// If someone already finished this broadcast, finish immediately
final int oldDeliveryState = getDeliveryState(r, index);
if (isDeliveryStateTerminal(oldDeliveryState)) {
- finishReceiverLocked(queue, oldDeliveryState);
+ enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
return;
}
// Consider additional cases where we'd want to finish immediately
if (app.isInFullBackup()) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
return;
}
if (mSkipPolicy.shouldSkip(r, receiver)) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
return;
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
return;
}
// Ignore registered receivers from a previous PID
if ((receiver instanceof BroadcastFilter)
&& ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
+ "BroadcastFilter for mismatched PID");
return;
}
- if (mService.mProcessesReady && !r.timeoutExempt) {
+ // Skip ANR tracking early during boot, when requested, or when we
+ // immediately assume delivery success
+ final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+ if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
@@ -775,7 +800,8 @@
}
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
- setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+ setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
+ "scheduleReceiverWarmLocked");
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
@@ -789,8 +815,9 @@
// TODO: consider making registered receivers of unordered
// broadcasts report results to detect ANRs
- if (!r.ordered) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+ if (assumeDelivered) {
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_DELIVERED,
+ "assuming delivered");
}
} else {
notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
@@ -804,10 +831,11 @@
logw(msg);
app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
app.setKilled(true);
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
}
} else {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "missing IApplicationThread");
}
}
@@ -851,7 +879,8 @@
}
private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+ "deliveryTimeoutHardLocked");
}
@Override
@@ -878,16 +907,17 @@
if (r.resultAbort) {
for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
setDeliveryState(null, null, r, i, r.receivers.get(i),
- BroadcastRecord.DELIVERY_SKIPPED);
+ BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
}
}
}
- return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+ return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
}
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
- @DeliveryState int deliveryState) {
+ @DeliveryState int deliveryState, @NonNull String reason) {
+ final int cookie = traceBegin("finishReceiver");
checkState(queue.isActive(), "isActive");
final ProcessRecord app = queue.app;
@@ -895,7 +925,7 @@
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
- setDeliveryState(queue, app, r, index, receiver, deliveryState);
+ setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
@@ -914,11 +944,12 @@
final boolean shouldRetire =
(queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+ final boolean res;
if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
// We're on a roll; move onto the next broadcast for this process
queue.makeActiveNextPending();
scheduleReceiverWarmLocked(queue);
- return true;
+ res = true;
} else {
// We've drained running broadcasts; maybe move back to runnable
queue.makeActiveIdle();
@@ -932,8 +963,10 @@
// Tell other OS components that app is not actively running, giving
// a chance to update OOM adjustment
notifyStoppedRunning(queue);
- return false;
+ res = false;
}
+ traceEnd(cookie);
+ return res;
}
/**
@@ -942,7 +975,8 @@
*/
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
@Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
- @NonNull Object receiver, @DeliveryState int newDeliveryState) {
+ @NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
+ final int cookie = traceBegin("setDeliveryState");
final int oldDeliveryState = getDeliveryState(r, index);
// Only apply state when we haven't already reached a terminal state;
@@ -970,7 +1004,7 @@
logw("Delivery state of " + r + " to " + receiver
+ " via " + app + " changed from "
+ deliveryStateToString(oldDeliveryState) + " to "
- + deliveryStateToString(newDeliveryState));
+ + deliveryStateToString(newDeliveryState) + " because " + reason);
}
r.terminalCount++;
@@ -1000,6 +1034,8 @@
enqueueUpdateRunningList();
}
}
+
+ traceEnd(cookie);
}
private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
@@ -1060,7 +1096,8 @@
* of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
- setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+ "mBroadcastConsumerSkip");
};
/**
@@ -1068,7 +1105,8 @@
* cancelled, usually as a result of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> {
- setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+ "mBroadcastConsumerSkipAndCanceled");
r.resultCode = Activity.RESULT_CANCELED;
r.resultData = null;
r.resultExtras = null;
@@ -1260,18 +1298,6 @@
}
}
- private int traceBegin(String trackName, String methodName) {
- final int cookie = methodName.hashCode();
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- trackName, methodName, cookie);
- return cookie;
- }
-
- private void traceEnd(String trackName, int cookie) {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- trackName, cookie);
- }
-
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid));
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 2a3c897..65f9b9b 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -617,6 +617,10 @@
return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
}
+ boolean isOffload() {
+ return (intent.getFlags() & Intent.FLAG_RECEIVER_OFFLOAD) != 0;
+ }
+
/**
* Core policy determination about this broadcast's delivery prioritization
*/
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 60fddf0..481ab17 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -21,6 +21,7 @@
import static com.android.server.am.BroadcastQueue.TAG;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -42,6 +43,8 @@
import com.android.internal.util.ArrayUtils;
+import java.util.Objects;
+
/**
* Policy logic that decides if delivery of a particular {@link BroadcastRecord}
* should be skipped for a given {@link ResolveInfo} or {@link BroadcastFilter}.
@@ -51,8 +54,8 @@
public class BroadcastSkipPolicy {
private final ActivityManagerService mService;
- public BroadcastSkipPolicy(ActivityManagerService service) {
- mService = service;
+ public BroadcastSkipPolicy(@NonNull ActivityManagerService service) {
+ mService = Objects.requireNonNull(service);
}
/**
@@ -60,18 +63,39 @@
* the given {@link BroadcastFilter} or {@link ResolveInfo}.
*/
public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) {
- if (target instanceof BroadcastFilter) {
- return shouldSkip(r, (BroadcastFilter) target);
+ final String msg = shouldSkipMessage(r, target);
+ if (msg != null) {
+ Slog.w(TAG, msg);
+ return true;
} else {
- return shouldSkip(r, (ResolveInfo) target);
+ return false;
+ }
+ }
+
+ /**
+ * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+ * the given {@link BroadcastFilter} or {@link ResolveInfo}.
+ *
+ * @return message indicating why the argument should be skipped, otherwise
+ * {@code null} if it can proceed.
+ */
+ public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) {
+ if (target instanceof BroadcastFilter) {
+ return shouldSkipMessage(r, (BroadcastFilter) target);
+ } else {
+ return shouldSkipMessage(r, (ResolveInfo) target);
}
}
/**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link ResolveInfo}.
+ *
+ * @return message indicating why the argument should be skipped, otherwise
+ * {@code null} if it can proceed.
*/
- public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
+ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+ @NonNull ResolveInfo info) {
final BroadcastOptions brOptions = r.options;
final ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
@@ -82,58 +106,52 @@
< brOptions.getMinManifestReceiverApiLevel() ||
info.activityInfo.applicationInfo.targetSdkVersion
> brOptions.getMaxManifestReceiverApiLevel())) {
- Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+ return "Target SDK mismatch: receiver " + info.activityInfo
+ " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+ " but delivery restricted to ["
+ brOptions.getMinManifestReceiverApiLevel() + ", "
+ brOptions.getMaxManifestReceiverApiLevel()
- + "] broadcasting " + broadcastDescription(r, component));
- return true;
+ + "] broadcasting " + broadcastDescription(r, component);
}
if (brOptions != null &&
!brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+ return "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+ " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
- + r.options.getRequireCompatChangeId());
- return true;
+ + r.options.getRequireCompatChangeId();
}
if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Association not allowed: broadcasting "
- + broadcastDescription(r, component));
- return true;
+ return "Association not allowed: broadcasting "
+ + broadcastDescription(r, component);
}
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Firewall blocked: broadcasting "
- + broadcastDescription(r, component));
- return true;
+ return "Firewall blocked: broadcasting "
+ + broadcastDescription(r, component);
}
int perm = checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
if (perm != PackageManager.PERMISSION_GRANTED) {
if (!info.activityInfo.exported) {
- Slog.w(TAG, "Permission Denial: broadcasting "
+ return "Permission Denial: broadcasting "
+ broadcastDescription(r, component)
- + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
+ + " is not exported from uid " + info.activityInfo.applicationInfo.uid;
} else {
- Slog.w(TAG, "Permission Denial: broadcasting "
+ return "Permission Denial: broadcasting "
+ broadcastDescription(r, component)
- + " requires " + info.activityInfo.permission);
+ + " requires " + info.activityInfo.permission;
}
- return true;
} else if (info.activityInfo.permission != null) {
final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
r.callingUid, r.callerPackage, r.callerFeatureId,
"Broadcast delivered to " + info.activityInfo.name)
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: broadcasting "
+ return "Appop Denial: broadcasting "
+ broadcastDescription(r, component)
+ " requires appop " + AppOpsManager.permissionToOp(
- info.activityInfo.permission));
- return true;
+ info.activityInfo.permission);
}
}
@@ -142,38 +160,34 @@
android.Manifest.permission.INTERACT_ACROSS_USERS,
info.activityInfo.applicationInfo.uid)
!= PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+ return "Permission Denial: Receiver " + component.flattenToShortString()
+ " requests FLAG_SINGLE_USER, but app does not hold "
- + android.Manifest.permission.INTERACT_ACROSS_USERS);
- return true;
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
}
}
if (info.activityInfo.applicationInfo.isInstantApp()
&& r.callingUid != info.activityInfo.applicationInfo.uid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent
+ " to " + component.flattenToShortString()
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")"
- + " Instant Apps do not support manifest receivers");
- return true;
+ + " Instant Apps do not support manifest receivers";
}
if (r.callerInstantApp
&& (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
&& r.callingUid != info.activityInfo.applicationInfo.uid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent
+ " to " + component.flattenToShortString()
+ " requires receiver have visibleToInstantApps set"
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
// If the target process is crashing, just skip it.
- Slog.w(TAG, "Skipping deliver ordered [" + r.queue.toString() + "] " + r
- + " to " + r.curApp + ": process crashing");
- return true;
+ return "Skipping deliver ordered [" + r.queue.toString() + "] " + r
+ + " to " + r.curApp + ": process crashing";
}
boolean isAvailable = false;
@@ -183,15 +197,13 @@
UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
} catch (Exception e) {
// all such failures mean we skip this receiver
- Slog.w(TAG, "Exception getting recipient info for "
- + info.activityInfo.packageName, e);
+ return "Exception getting recipient info for "
+ + info.activityInfo.packageName;
}
if (!isAvailable) {
- Slog.w(TAG,
- "Skipping delivery to " + info.activityInfo.packageName + " / "
+ return "Skipping delivery to " + info.activityInfo.packageName + " / "
+ info.activityInfo.applicationInfo.uid
- + " : package no longer available");
- return true;
+ + " : package no longer available";
}
// If permissions need a review before any of the app components can run, we drop
@@ -201,10 +213,8 @@
if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
info.activityInfo.packageName, UserHandle.getUserId(
info.activityInfo.applicationInfo.uid))) {
- Slog.w(TAG,
- "Skipping delivery: permission review required for "
- + broadcastDescription(r, component));
- return true;
+ return "Skipping delivery: permission review required for "
+ + broadcastDescription(r, component);
}
final int allowed = mService.getAppStartModeLOSP(
@@ -216,10 +226,9 @@
// to it and the app is in a state that should not receive it
// (depending on how getAppStartModeLOSP has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
- Slog.w(TAG, "Background execution disabled: receiving "
+ return "Background execution disabled: receiving "
+ r.intent + " to "
- + component.flattenToShortString());
- return true;
+ + component.flattenToShortString();
} else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
@@ -228,10 +237,9 @@
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
- Slog.w(TAG, "Background execution not allowed: receiving "
+ return "Background execution not allowed: receiving "
+ r.intent + " to "
- + component.flattenToShortString());
- return true;
+ + component.flattenToShortString();
}
}
@@ -239,10 +247,8 @@
&& !mService.mUserController
.isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
0 /* flags */)) {
- Slog.w(TAG,
- "Skipping delivery to " + info.activityInfo.packageName + " / "
- + info.activityInfo.applicationInfo.uid + " : user is not running");
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName + " / "
+ + info.activityInfo.applicationInfo.uid + " : user is not running";
}
if (r.excludedPermissions != null && r.excludedPermissions.length > 0) {
@@ -268,13 +274,15 @@
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName)
== AppOpsManager.MODE_ALLOWED)) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to excluded permission " + excludedPermission;
}
} else {
// When there is no app op associated with the permission,
// skip when permission is granted.
if (perm == PackageManager.PERMISSION_GRANTED) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to excluded permission " + excludedPermission;
}
}
}
@@ -283,13 +291,12 @@
// Check that the receiver does *not* belong to any of the excluded packages
if (r.excludedPackages != null && r.excludedPackages.length > 0) {
if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
- Slog.w(TAG, "Skipping delivery of excluded package "
+ return "Skipping delivery of excluded package "
+ r.intent + " to "
+ component.flattenToShortString()
+ " excludes package " + component.getPackageName()
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
@@ -307,95 +314,94 @@
perm = PackageManager.PERMISSION_DENIED;
}
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
+ return "Permission Denial: receiving "
+ r.intent + " to "
+ component.flattenToShortString()
+ " requires " + requiredPermission
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
if (!noteOpForManifestReceiver(appOp, r, info, component)) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to required appop " + appOp;
}
}
}
}
if (r.appOp != AppOpsManager.OP_NONE) {
if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to required appop " + r.appOp;
}
}
- return false;
+ return null;
}
/**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link BroadcastFilter}.
+ *
+ * @return message indicating why the argument should be skipped, otherwise
+ * {@code null} if it can proceed.
*/
- public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull BroadcastFilter filter) {
+ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+ @NonNull BroadcastFilter filter) {
if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
- Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
+ return "Compat change filtered: broadcasting " + r.intent.toString()
+ " to uid " + filter.owningUid + " due to compat change "
- + r.options.getRequireCompatChangeId());
- return true;
+ + r.options.getRequireCompatChangeId();
}
if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
filter.packageName, filter.owningUid)) {
- Slog.w(TAG, "Association not allowed: broadcasting "
+ return "Association not allowed: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid=" + r.callingPid
+ ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
- + filter);
- return true;
+ + filter;
}
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, filter.receiverList.uid)) {
- Slog.w(TAG, "Firewall blocked: broadcasting "
+ return "Firewall blocked: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid=" + r.callingPid
+ ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
- + filter);
- return true;
+ + filter;
}
// Check that the sender has permission to send to this receiver
if (filter.requiredPermission != null) {
int perm = checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: broadcasting "
+ return "Permission Denial: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid="
+ r.callingPid + ", uid=" + r.callingUid + ")"
+ " requires " + filter.requiredPermission
- + " due to registered receiver " + filter);
- return true;
+ + " due to registered receiver " + filter;
} else {
final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
if (opCode != AppOpsManager.OP_NONE
&& mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: broadcasting "
+ return "Appop Denial: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid="
+ r.callingPid + ", uid=" + r.callingUid + ")"
+ " requires appop " + AppOpsManager.permissionToOp(
filter.requiredPermission)
- + " due to registered receiver " + filter);
- return true;
+ + " due to registered receiver " + filter;
}
}
}
if ((filter.receiverList.app == null || filter.receiverList.app.isKilled()
|| filter.receiverList.app.mErrorState.isCrashing())) {
- Slog.w(TAG, "Skipping deliver [" + r.queue.toString() + "] " + r
- + " to " + filter.receiverList + ": process gone or crashing");
- return true;
+ return "Skipping deliver [" + r.queue.toString() + "] " + r
+ + " to " + filter.receiverList + ": process gone or crashing";
}
// Ensure that broadcasts are only sent to other Instant Apps if they are marked as
@@ -405,28 +411,26 @@
if (!visibleToInstantApps && filter.instantApp
&& filter.receiverList.uid != r.callingUid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")"
- + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
- return true;
+ + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS";
}
if (!filter.visibleToInstantApp && r.callerInstantApp
&& filter.receiverList.uid != r.callingUid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " requires receiver be visible to instant apps"
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
// Check that the receiver has the required permission(s) to receive this broadcast.
@@ -436,15 +440,14 @@
int perm = checkComponentPermission(requiredPermission,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
+ return "Permission Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " requires " + requiredPermission
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
@@ -452,7 +455,7 @@
filter.receiverList.uid, filter.packageName, filter.featureId,
"Broadcast delivered to registered receiver " + filter.receiverId)
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: receiving "
+ return "Appop Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
@@ -460,8 +463,7 @@
+ " requires appop " + AppOpsManager.permissionToOp(
requiredPermission)
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
}
@@ -469,14 +471,13 @@
int perm = checkComponentPermission(null,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: security check failed when receiving "
+ return "Permission Denial: security check failed when receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
// Check that the receiver does *not* have any excluded permissions
@@ -496,7 +497,7 @@
filter.receiverList.uid,
filter.packageName)
== AppOpsManager.MODE_ALLOWED)) {
- Slog.w(TAG, "Appop Denial: receiving "
+ return "Appop Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
@@ -504,22 +505,20 @@
+ " excludes appop " + AppOpsManager.permissionToOp(
excludedPermission)
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
} else {
// When there is no app op associated with the permission,
// skip when permission is granted.
if (perm == PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
+ return "Permission Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " excludes " + excludedPermission
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
}
@@ -528,15 +527,14 @@
// Check that the receiver does *not* belong to any of the excluded packages
if (r.excludedPackages != null && r.excludedPackages.length > 0) {
if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
- Slog.w(TAG, "Skipping delivery of excluded package "
+ return "Skipping delivery of excluded package "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " excludes package " + filter.packageName
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
@@ -546,15 +544,14 @@
filter.receiverList.uid, filter.packageName, filter.featureId,
"Broadcast delivered to registered receiver " + filter.receiverId)
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: receiving "
+ return "Appop Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " requires appop " + AppOpsManager.opToName(r.appOp)
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
@@ -562,15 +559,14 @@
if (!filter.exported && checkComponentPermission(null, r.callingPid,
r.callingUid, filter.receiverList.uid, filter.exported)
!= PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Exported Denial: sending "
+ return "Exported Denial: sending "
+ r.intent.toString()
+ ", action: " + r.intent.getAction()
+ " from " + r.callerPackage
+ " (uid=" + r.callingUid + ")"
+ " due to receiver " + filter.receiverList.app
+ " (uid " + filter.receiverList.uid + ")"
- + " not specifying RECEIVER_EXPORTED");
- return true;
+ + " not specifying RECEIVER_EXPORTED";
}
// If permissions need a review before any of the app components can run, we drop
@@ -579,10 +575,10 @@
// broadcast.
if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
filter.owningUserId)) {
- return true;
+ return "Skipping delivery to " + filter.packageName + " due to permissions review";
}
- return false;
+ return null;
}
private static String broadcastDescription(BroadcastRecord r, ComponentName component) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 82d239f..8d3890c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2185,8 +2185,6 @@
mHandler.sendMessage(mHandler.obtainMessage(COMPLETE_USER_SWITCH_MSG, newUserId, 0));
uss.switching = false;
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
if (oldUserId == UserHandle.USER_SYSTEM) {
@@ -2200,21 +2198,22 @@
@VisibleForTesting
void completeUserSwitch(int newUserId) {
- if (isUserSwitchUiEnabled()) {
- // If there is no challenge set, dismiss the keyguard right away
- if (!mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
- // Wait until the keyguard is dismissed to unfreeze
- mInjector.dismissKeyguard(
- new Runnable() {
- public void run() {
- unfreezeScreen();
- }
- },
- "User Switch");
- return;
- } else {
+ final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
+ final Runnable runnable = () -> {
+ if (isUserSwitchUiEnabled) {
unfreezeScreen();
}
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
+ };
+
+ // 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");
+ } else {
+ runnable.run();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0eaa5e4..1225d99 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4413,7 +4413,7 @@
* a stylus deviceId is not already registered on device.
*/
@BinderThread
- @EnforcePermission(Manifest.permission.INJECT_EVENTS)
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 80c9803..a64bd69 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -469,6 +469,48 @@
}
/**
+ * Records that a particular container has been reparented. This only effects windows that have
+ * already been collected in the transition. This should be called before reparenting because
+ * the old parent may be removed during reparenting, for example:
+ * {@link Task#shouldRemoveSelfOnLastChildRemoval}
+ */
+ void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
+ if (!mChanges.containsKey(wc)) {
+ // #collectReparentChange() will be called when the window is reparented. Skip if it is
+ // a window that has not been collected, which means we don't care about this window for
+ // the current transition.
+ return;
+ }
+ final ChangeInfo change = mChanges.get(wc);
+ // Use the current common ancestor if there are multiple reparent, and the original parent
+ // has been detached. Otherwise, use the original parent before the transition.
+ final WindowContainer prevParent =
+ change.mStartParent == null || change.mStartParent.isAttached()
+ ? change.mStartParent
+ : change.mCommonAncestor;
+ if (prevParent == null || !prevParent.isAttached()) {
+ Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has"
+ + " been detached: " + wc);
+ return;
+ }
+ if (prevParent == newParent) {
+ Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: "
+ + wc);
+ return;
+ }
+ if (!newParent.isAttached()) {
+ Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after"
+ + " reparenting: " + wc);
+ return;
+ }
+ WindowContainer ancestor = newParent;
+ while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
+ ancestor = ancestor.getParent();
+ }
+ change.mCommonAncestor = ancestor;
+ }
+
+ /**
* @return {@code true} if `wc` is a participant or is a descendant of one.
*/
boolean isInTransition(WindowContainer wc) {
@@ -830,8 +872,8 @@
void abort() {
// This calls back into itself via controller.abort, so just early return here.
if (mState == STATE_ABORT) return;
- if (mState != STATE_COLLECTING) {
- throw new IllegalStateException("Too late to abort.");
+ if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
+ throw new IllegalStateException("Too late to abort. state=" + mState);
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
mState = STATE_ABORT;
@@ -1524,20 +1566,7 @@
return out;
}
- // Find the top-most shared ancestor of app targets.
- WindowContainer<?> ancestor = topApp.getParent();
- // Go up ancestor parent chain until all targets are descendants.
- ancestorLoop:
- while (ancestor != null) {
- for (int i = sortedTargets.size() - 1; i >= 0; --i) {
- final WindowContainer wc = sortedTargets.get(i);
- if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) {
- ancestor = ancestor.getParent();
- continue ancestorLoop;
- }
- }
- break;
- }
+ WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
// make leash based on highest (z-order) direct child of ancestor with a participant.
WindowContainer leashReference = sortedTargets.get(0);
@@ -1654,6 +1683,46 @@
return out;
}
+ /**
+ * Finds the top-most common ancestor of app targets.
+ *
+ * Makes sure that the previous parent is also a descendant to make sure the animation won't
+ * be covered by other windows below the previous parent. For example, when reparenting an
+ * activity from PiP Task to split screen Task.
+ */
+ @NonNull
+ private static WindowContainer<?> findCommonAncestor(
+ @NonNull ArrayList<WindowContainer> targets,
+ @NonNull ArrayMap<WindowContainer, ChangeInfo> changes,
+ @NonNull WindowContainer<?> topApp) {
+ WindowContainer<?> ancestor = topApp.getParent();
+ // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
+ // null because all targets are attached.
+ for (int i = targets.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = targets.get(i);
+ if (isWallpaper(wc)) {
+ // Skip the non-app window.
+ continue;
+ }
+ while (!wc.isDescendantOf(ancestor)) {
+ ancestor = ancestor.getParent();
+ }
+
+ // Make sure the previous parent is also a descendant to make sure the animation won't
+ // be covered by other windows below the previous parent. For example, when reparenting
+ // an activity from PiP Task to split screen Task.
+ final ChangeInfo change = changes.get(wc);
+ final WindowContainer prevParent = change.mCommonAncestor;
+ if (prevParent == null || !prevParent.isAttached()) {
+ continue;
+ }
+ while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
+ ancestor = ancestor.getParent();
+ }
+ }
+ return ancestor;
+ }
+
private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
ArrayList<WindowContainer> sortedTargets) {
// Find the layout params of the top-most application window that is part of the
@@ -1772,10 +1841,19 @@
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
- // Usually "post" change state.
+ /**
+ * "Parent" that is also included in the transition. When populating the parent changes, we
+ * may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
+ */
WindowContainer mEndParent;
- // Parent before change state.
+ /** Actual parent window before change state. */
WindowContainer mStartParent;
+ /**
+ * When the window is reparented during the transition, this is the common ancestor window
+ * of the {@link #mStartParent} and the current parent. This is needed because the
+ * {@link #mStartParent} may have been detached when the transition starts.
+ */
+ WindowContainer mCommonAncestor;
// State tracking
boolean mExistenceChanged = false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ac85c9a..37bef3a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -533,6 +533,17 @@
mCollectingTransition.collectVisibleChange(wc);
}
+ /**
+ * Records that a particular container has been reparented. This only effects windows that have
+ * already been collected in the transition. This should be called before reparenting because
+ * the old parent may be removed during reparenting, for example:
+ * {@link Task#shouldRemoveSelfOnLastChildRemoval}
+ */
+ void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
+ if (!isCollecting()) return;
+ mCollectingTransition.collectReparentChange(wc, newParent);
+ }
+
/** @see Transition#mStatusBarTransitionDelay */
void setStatusBarTransitionDelay(long delay) {
if (mCollectingTransition == null) return;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0b5de85..73d4496 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -542,6 +542,10 @@
throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
}
+ // Collect before removing child from old parent, because the old parent may be removed if
+ // this is the last child in it.
+ mTransitionController.collectReparentChange(this, newParent);
+
// The display object before reparenting as that might lead to old parent getting removed
// from the display if it no longer has any child.
final DisplayContent prevDc = oldParent.getDisplayContent();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 5f25e3d..dcf094f 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -83,8 +83,9 @@
*/
public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In show");
- Intent intent = IntentFactory.newIntent(requestInfo, providerDataList,
- mResultReceiver);
+ Intent intent = IntentFactory.newIntent(
+ requestInfo, providerDataList,
+ new ArrayList<>(), mResultReceiver);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 24610df..ff2107a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.app.slice.Slice;
import android.credentials.ui.Entry;
-import android.credentials.ui.ProviderData;
+import android.credentials.ui.GetCredentialProviderData;
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
@@ -117,7 +117,7 @@
}
@Override
- protected final ProviderData prepareUiData() throws IllegalArgumentException {
+ protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isCompletionStatus(getStatus())) {
Log.i(TAG, "In prepareUiData not complete");
@@ -147,7 +147,7 @@
* To be called by {@link ProviderGetSession} when the UI is to be invoked.
*/
@Nullable
- private ProviderData prepareUiProviderDataWithCredentials(@NonNull
+ private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull
CredentialsDisplayContent content) {
Log.i(TAG, "in prepareUiProviderData");
List<Entry> credentialEntries = new ArrayList<>();
@@ -173,15 +173,10 @@
action.getSlice()));
}
- // TODO : Set the correct last used time
- return new ProviderData.Builder(mComponentName.flattenToString(),
- mProviderInfo.getServiceLabel() == null ? "" :
- mProviderInfo.getServiceLabel().toString(),
- /*icon=*/null)
+ return new GetCredentialProviderData.Builder(mComponentName.flattenToString())
.setCredentialEntries(credentialEntries)
.setActionChips(actionChips)
.setAuthenticationEntry(authenticationEntry)
- .setLastUsedTimeMillis(0)
.build();
}
@@ -189,7 +184,7 @@
* To be called by {@link ProviderGetSession} when the UI is to be invoked.
*/
@Nullable
- private ProviderData prepareUiProviderDataWithAuthentication(@NonNull
+ private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull
Action authenticationEntry) {
// TODO : Implement authentication flow
return null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e1a4c1d..de59603 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -280,13 +280,13 @@
constants.TIMEOUT = 100;
constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
- public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
+ public boolean shouldSkip(BroadcastRecord r, Object o) {
// Ignored
return false;
}
- public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) {
+ public String shouldSkipMessage(BroadcastRecord r, Object o) {
// Ignored
- return false;
+ return null;
}
};
final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d4c9087..35b9710 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1520,6 +1520,29 @@
transition.abort();
}
+ @Test
+ public void testCollectReparentChange() {
+ registerTestTransitionPlayer();
+
+ // Reparent activity in transition.
+ final Task lastParent = createTask(mDisplayContent);
+ final Task newParent = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(lastParent);
+ doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
+ doNothing().when(activity).setDropInputMode(anyInt());
+ activity.mVisibleRequested = true;
+
+ final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
+ activity.mTransitionController, mWm.mSyncEngine);
+ activity.mTransitionController.moveToCollecting(transition);
+ transition.collect(activity);
+ activity.reparent(newParent, POSITION_TOP);
+
+ // ChangeInfo#mCommonAncestor should be set after reparent.
+ final Transition.ChangeInfo change = transition.mChanges.get(activity);
+ assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2c7867c..7be40b8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8774,6 +8774,22 @@
"premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
/**
+ * The amount of time in milliseconds within which the network must set up a slicing
+ * configuration for the premium capability after
+ * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+ * returns {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS}.
+ * During the setup time, calls to
+ * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} will return
+ * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+ * If the network fails set up a slicing configuration for the premium capability within the
+ * setup time, subsequent purchase requests will be allowed to go through again.
+ *
+ * The default value is 5 minutes.
+ */
+ public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG =
+ "premium_capability_network_setup_time_millis_long";
+
+ /**
* The URL to redirect to when the user clicks on the notification for a network boost via
* premium capabilities after applications call
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}.
@@ -9496,6 +9512,8 @@
sDefaults.putLong(
KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
+ sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG,
+ TimeUnit.MINUTES.toMillis(5));
sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null);
sDefaults.putBoolean(KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL, false);
sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ef693b5..76a145c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4002,6 +4002,10 @@
* cautiously, for example, after formatting the number to a consistent format with
* {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
*
+ * <p>The availability and correctness of the phone number depends on the underlying source
+ * and the network etc. Additional verification is needed to use this number for
+ * security-related or other sensitive scenarios.
+ *
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @return the phone number, or an empty string if not available.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5f1d086..35b2055 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17206,7 +17206,13 @@
}
/**
- * Purchase premium capability request was successful. Subsequent attempts will return
+ * Purchase premium capability request was successful.
+ * Once the purchase result is successful, the network must set up a slicing configuration
+ * for the purchased premium capability within the timeout specified by
+ * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
+ * During the setup time, subsequent attempts will return
+ * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+ * After setup is complete, subsequent attempts will return
* {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires.
* The expiry time is determined by the type or duration of boost purchased from the carrier,
* provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}.
@@ -17330,6 +17336,16 @@
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
/**
+ * Purchase premium capability was successful and is waiting for the network to setup the
+ * slicing configuration. If the setup is complete within the time specified by
+ * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG},
+ * subsequent requests will return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}
+ * until the purchase expires. If the setup is not complete within the time specified above,
+ * applications can reques the premium capability again.
+ */
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
+
+ /**
* Results of the purchase premium capability request.
* @hide
*/
@@ -17347,7 +17363,8 @@
PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA})
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
public @interface PurchasePremiumCapabilityResult {}
/**
@@ -17388,6 +17405,8 @@
return "NETWORK_CONGESTED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
return "NOT_DEFAULT_DATA";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
+ return "PENDING_NETWORK_SETUP";
default:
return "UNKNOWN (" + result + ")";
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 09d7637..0edbc86 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -97,8 +97,8 @@
super.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
- @Postsubmit
@Test
+ @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index c10b993..4ee1283 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
@@ -71,7 +72,7 @@
}
@Test
- @Postsubmit
+ @FlakyTest(bugId = 227143265)
fun showWhenLockedAppWindowBecomesVisible() {
testSpec.assertWm {
this.hasNoVisibleAppWindow()
@@ -83,7 +84,7 @@
}
@Test
- @Postsubmit
+ @FlakyTest(bugId = 227143265)
fun showWhenLockedAppLayerBecomesVisible() {
testSpec.assertLayers {
this.isInvisible(showWhenLockedApp)
@@ -98,11 +99,17 @@
@Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 227143265)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 209599395)
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
companion object {
/**
* Creates the test configurations.