Merge "Provide isUserInLockdown through BiometricSettingsRepository" into udc-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index b92295a..51c6f6a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43413,6 +43413,8 @@
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
+ field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
@@ -50901,6 +50903,10 @@
field public static final int KEYCODE_LAST_CHANNEL = 229; // 0xe5
field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47
field public static final int KEYCODE_M = 41; // 0x29
+ field public static final int KEYCODE_MACRO_1 = 313; // 0x139
+ field public static final int KEYCODE_MACRO_2 = 314; // 0x13a
+ field public static final int KEYCODE_MACRO_3 = 315; // 0x13b
+ field public static final int KEYCODE_MACRO_4 = 316; // 0x13c
field public static final int KEYCODE_MANNER_MODE = 205; // 0xcd
field public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222; // 0xde
field public static final int KEYCODE_MEDIA_CLOSE = 128; // 0x80
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e93467b..1a8ebb1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -915,6 +915,12 @@
field public static final long FORCE_NON_RESIZE_APP = 181136395L; // 0xacbec0bL
field public static final long FORCE_RESIZE_APP = 174042936L; // 0xa5faf38L
field public static final long NEVER_SANDBOX_DISPLAY_APIS = 184838306L; // 0xb0468a2L
+ field public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION = 263959004L; // 0xfbbb1dcL
+ field public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // 0xfc0f74bL
+ field public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // 0xfc0ec12L
+ field public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L; // 0xfb1048bL
+ field public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION = 254631730L; // 0xf2d5f32L
+ field public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L; // 0xfdcbe7fL
field public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // 0xa5faf64L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN = 218959984L; // 0xd0d1070L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_LARGE = 180326787L; // 0xabf9183L
@@ -924,6 +930,9 @@
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
+ field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L; // 0xfd27655L
+ field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; // 0xfd27b38L
+ field public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; // 0xf4156bcL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -3332,7 +3341,7 @@
method public static String actionToString(int);
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 312; // 0x138
+ field public static final int LAST_KEYCODE = 316; // 0x13c
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index d23d3cd..6357798 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -37,8 +37,6 @@
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -61,7 +59,8 @@
private long mRequireCompatChangeId = CHANGE_INVALID;
private long mIdForResponseEvent;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
- private @Nullable String mDeliveryGroupMatchingKey;
+ private @Nullable String mDeliveryGroupMatchingNamespaceFragment;
+ private @Nullable String mDeliveryGroupMatchingKeyFragment;
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
private @DeferralPolicy int mDeferralPolicy;
@@ -196,7 +195,13 @@
"android:broadcast.deliveryGroupPolicy";
/**
- * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
+ * Corresponds to namespace fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_NAMESPACE =
+ "android:broadcast.deliveryGroupMatchingNamespace";
+
+ /**
+ * Corresponds to key fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
*/
private static final String KEY_DELIVERY_GROUP_KEY =
"android:broadcast.deliveryGroupMatchingKey";
@@ -337,7 +342,8 @@
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
- mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+ mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
+ mDeliveryGroupMatchingKeyFragment = opts.getString(KEY_DELIVERY_GROUP_KEY);
mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
BundleMerger.class);
mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
@@ -851,11 +857,8 @@
@NonNull
public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
@NonNull String key) {
- Preconditions.checkArgument(!namespace.contains(":"),
- "namespace should not contain ':'");
- Preconditions.checkArgument(!key.contains(":"),
- "key should not contain ':'");
- mDeliveryGroupMatchingKey = namespace + ":" + key;
+ mDeliveryGroupMatchingNamespaceFragment = Objects.requireNonNull(namespace);
+ mDeliveryGroupMatchingKeyFragment = Objects.requireNonNull(key);
return this;
}
@@ -868,7 +871,38 @@
*/
@Nullable
public String getDeliveryGroupMatchingKey() {
- return mDeliveryGroupMatchingKey;
+ if (mDeliveryGroupMatchingNamespaceFragment == null
+ || mDeliveryGroupMatchingKeyFragment == null) {
+ return null;
+ }
+ return String.join(":", mDeliveryGroupMatchingNamespaceFragment,
+ mDeliveryGroupMatchingKeyFragment);
+ }
+
+ /**
+ * Return the namespace fragment that is used to identify the delivery group that this
+ * broadcast belongs to.
+ *
+ * @return the delivery group namespace fragment that was previously set using
+ * {@link #setDeliveryGroupMatchingKey(String, String)}.
+ * @hide
+ */
+ @Nullable
+ public String getDeliveryGroupMatchingNamespaceFragment() {
+ return mDeliveryGroupMatchingNamespaceFragment;
+ }
+
+ /**
+ * Return the key fragment that is used to identify the delivery group that this
+ * broadcast belongs to.
+ *
+ * @return the delivery group key fragment that was previously set using
+ * {@link #setDeliveryGroupMatchingKey(String, String)}.
+ * @hide
+ */
+ @Nullable
+ public String getDeliveryGroupMatchingKeyFragment() {
+ return mDeliveryGroupMatchingKeyFragment;
}
/**
@@ -876,7 +910,8 @@
* {@link #setDeliveryGroupMatchingKey(String, String)}.
*/
public void clearDeliveryGroupMatchingKey() {
- mDeliveryGroupMatchingKey = null;
+ mDeliveryGroupMatchingNamespaceFragment = null;
+ mDeliveryGroupMatchingKeyFragment = null;
}
/**
@@ -1094,8 +1129,11 @@
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
- if (mDeliveryGroupMatchingKey != null) {
- b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
+ if (mDeliveryGroupMatchingNamespaceFragment != null) {
+ b.putString(KEY_DELIVERY_GROUP_NAMESPACE, mDeliveryGroupMatchingNamespaceFragment);
+ }
+ if (mDeliveryGroupMatchingKeyFragment != null) {
+ b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKeyFragment);
}
if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
if (mDeliveryGroupExtrasMerger != null) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8aa9b73..b5d2f2c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1083,6 +1083,7 @@
@ChangeId
@Overridable
@Disabled
+ @TestApi
public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION =
254631730L; // buganizer id
@@ -1142,6 +1143,7 @@
@ChangeId
@Overridable
@Disabled
+ @TestApi
public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION =
263959004L; // buganizer id
@@ -1154,6 +1156,7 @@
@ChangeId
@Overridable
@Disabled
+ @TestApi
public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id
/**
@@ -1166,6 +1169,7 @@
@ChangeId
@Overridable
@Disabled
+ @TestApi
public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
264301586L; // buganizer id
@@ -1292,6 +1296,7 @@
@ChangeId
@Disabled
@Overridable
+ @TestApi
public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L;
// Compat framework that per-app overrides rely on only supports booleans. That's why we have
@@ -1307,6 +1312,7 @@
@ChangeId
@Disabled
@Overridable
+ @TestApi
public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L;
/**
@@ -1318,6 +1324,7 @@
@ChangeId
@Disabled
@Overridable
+ @TestApi
public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L;
/**
@@ -1331,6 +1338,7 @@
@ChangeId
@Disabled
@Overridable
+ @TestApi
public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L;
/**
@@ -1377,6 +1385,7 @@
@ChangeId
@Disabled
@Overridable
+ @TestApi
public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
/**
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 2af0254..b6d9400 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -897,13 +897,38 @@
* This key is handled by the framework and is never delivered to applications.
*/
public static final int KEYCODE_RECENT_APPS = 312;
+ /**
+ * Key code constant: A button whose usage can be customized by the user through
+ * the system.
+ * User customizable key #1.
+ */
+ public static final int KEYCODE_MACRO_1 = 313;
+ /**
+ * Key code constant: A button whose usage can be customized by the user through
+ * the system.
+ * User customizable key #2.
+ */
+ public static final int KEYCODE_MACRO_2 = 314;
+ /**
+ * Key code constant: A button whose usage can be customized by the user through
+ * the system.
+ * User customizable key #3.
+ */
+ public static final int KEYCODE_MACRO_3 = 315;
+ /**
+ * Key code constant: A button whose usage can be customized by the user through
+ * the system.
+ * User customizable key #4.
+ */
+ public static final int KEYCODE_MACRO_4 = 316;
+
/**
* Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
* @hide
*/
@TestApi
- public static final int LAST_KEYCODE = KEYCODE_RECENT_APPS;
+ public static final int LAST_KEYCODE = KEYCODE_MACRO_4;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 82e1777..d1f7b63 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -389,6 +389,7 @@
optional bool provides_max_bounds = 34;
optional bool enable_recents_screenshot = 35;
optional int32 last_drop_input_mode = 36;
+ optional int32 override_orientation = 37 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"];
}
/* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f0ef14..393e778 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1374,8 +1374,7 @@
android:permissionFlags="hardRestricted"
android:protectionLevel="dangerous" />
- <!-- Allows an application to write (but not read) the user's
- call log data.
+ <!-- Allows an application to write and read the user's call log data.
<p class="note"><strong>Note:</strong> If your app uses the
{@link #WRITE_CONTACTS} permission and <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 247d484..e27cd97 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -424,6 +424,10 @@
key 582 VOICE_ASSIST
# Linux KEY_ASSISTANT
key 583 ASSIST
+key 656 MACRO_1
+key 657 MACRO_2
+key 658 MACRO_3
+key 659 MACRO_4
# Keys defined by HID usages
key usage 0x0c0067 WINDOW
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index b93cc75..6312913 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -23,8 +23,8 @@
<bool name="def_airplane_mode_on">false</bool>
<bool name="def_theater_mode_on">false</bool>
<!-- Comma-separated list of bluetooth, wifi, and cell. -->
- <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string>
- <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string>
+ <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,uwb,wifi,wimax</string>
+ <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi</string>
<string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
<bool name="def_auto_time">true</bool>
<bool name="def_auto_time_zone">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 721b3c4..e736253 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3739,7 +3739,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 215;
+ private static final int SETTINGS_VERSION = 216;
private final int mUserId;
@@ -5737,6 +5737,31 @@
currentVersion = 215;
}
+ if (currentVersion == 215) {
+ // Version 215: default |def_airplane_mode_radios| and
+ // |airplane_mode_toggleable_radios| changed to remove NFC & add UWB.
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ final String oldApmRadiosValue = globalSettings.getSettingLocked(
+ Settings.Global.AIRPLANE_MODE_RADIOS).getValue();
+ if (TextUtils.equals("cell,bluetooth,wifi,nfc,wimax", oldApmRadiosValue)) {
+ globalSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Global.AIRPLANE_MODE_RADIOS,
+ getContext().getResources().getString(
+ R.string.def_airplane_mode_radios),
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ final String oldApmToggleableRadiosValue = globalSettings.getSettingLocked(
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS).getValue();
+ if (TextUtils.equals("bluetooth,wifi,nfc", oldApmToggleableRadiosValue)) {
+ globalSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
+ getContext().getResources().getString(
+ R.string.airplane_mode_toggleable_radios),
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 216;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d92e65c..ff57052 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -947,18 +947,6 @@
android:visibleToInstantApps="true">
</activity>
- <activity android:name=".user.UserSwitcherActivity"
- android:label="@string/accessibility_multi_user_switch_switcher"
- android:theme="@style/Theme.UserSwitcherActivity"
- android:excludeFromRecents="true"
- android:showWhenLocked="true"
- android:showForAllUsers="true"
- android:finishOnTaskLaunch="true"
- android:lockTaskMode="always"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
- android:visibleToInstantApps="true">
- </activity>
-
<receiver android:name=".controls.management.ControlsRequestReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
index b9509eb..01cba42 100644
--- a/packages/SystemUI/docs/user-switching.md
+++ b/packages/SystemUI/docs/user-switching.md
@@ -6,7 +6,7 @@
### Quick Settings
-In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherActivity][5]).
+In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherFullscreenDialog][5]).
### Bouncer
@@ -29,7 +29,7 @@
## Visual Components
-### [UserSwitcherActivity][5]
+### [UserSwitcherFullscreenDialog][5]
A fullscreen user switching activity, supporting add guest/user actions if configured.
@@ -41,5 +41,5 @@
[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
-[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
[6]: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2fb1592..a3655c31 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -844,12 +844,10 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
- <style name="Theme.UserSwitcherActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+ <style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
<item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
<item name="android:navigationBarColor">@color/user_switcher_fullscreen_bg</item>
- <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
- <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
<style name="Theme.CreateUser" parent="@android:style/Theme.DeviceDefault.NoActionBar">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index a010c9a..d221e22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -157,15 +156,6 @@
// TODO: Remove this workaround by ensuring such a race condition never happens.
mMainExecutor.executeDelayed(
this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
- mView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
- @Override
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- if (!mKeyguardViewController.isBouncerShowing()) {
- mView.hideKeyboard();
- }
- return insets;
- }
- });
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 30321f7..e1bca89 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -99,11 +99,13 @@
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.usb.UsbManager;
import android.nfc.NfcAdapter;
import android.os.CancellationSignal;
@@ -172,6 +174,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -1899,8 +1902,9 @@
CancellationSignal mFingerprintCancelSignal;
@VisibleForTesting
CancellationSignal mFaceCancelSignal;
- private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
- private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
+ private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties =
+ Collections.emptyList();
+ private List<FaceSensorPropertiesInternal> mFaceSensorProperties = Collections.emptyList();
private boolean mFingerprintLockedOut;
private boolean mFingerprintLockedOutPermanent;
private boolean mFaceLockedOutPermanent;
@@ -2366,11 +2370,29 @@
setStrongAuthTracker(mStrongAuthTracker);
if (mFpm != null) {
- mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
+ mFpm.addAuthenticatorsRegisteredCallback(
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors)
+ throws RemoteException {
+ mFingerprintSensorProperties = sensors;
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ mLogger.d("FingerprintManager onAllAuthenticatorsRegistered");
+ }
+ });
mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
}
if (mFaceManager != null) {
- mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
+ mFaceManager.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) throws RemoteException {
+ mFaceSensorProperties = sensors;
+ mLogger.d("FaceManager onAllAuthenticatorsRegistered");
+ }
+ });
mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 79a51d6..7ffb594 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -701,6 +701,6 @@
// TODO(b/272805037): Tracking Bug
@JvmField
- val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
- namespace = "vpn", teamfood = true)
+ val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature",
+ namespace = "vpn")
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 8387c1d..b394a07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -89,7 +89,7 @@
fun showSettings(expandable: Expandable)
/** Show the user switcher. */
- fun showUserSwitcher(context: Context, expandable: Expandable)
+ fun showUserSwitcher(expandable: Expandable)
}
@SysUISingleton
@@ -177,7 +177,7 @@
)
}
- override fun showUserSwitcher(context: Context, expandable: Expandable) {
- userInteractor.showUserSwitcher(context, expandable)
+ override fun showUserSwitcher(expandable: Expandable) {
+ userInteractor.showUserSwitcher(expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index f170ac1..3a9098a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -230,7 +230,7 @@
return
}
- footerActionsInteractor.showUserSwitcher(context, expandable)
+ footerActionsInteractor.showUserSwitcher(expandable)
}
private fun onSettingsButtonClicked(expandable: Expandable) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index f7c8bac..b2bf972 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.user;
-import android.app.Activity;
import android.os.UserHandle;
import com.android.settingslib.users.EditUserInfoController;
@@ -24,11 +23,8 @@
import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
import com.android.systemui.user.ui.dialog.UserDialogModule;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
/**
* Dagger module for User related classes.
@@ -49,12 +45,6 @@
return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
}
- /** Provides UserSwitcherActivity */
- @Binds
- @IntoMap
- @ClassKey(UserSwitcherActivity.class)
- public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
-
/**
* Provides the {@link UserHandle} for the user associated with this System UI process.
*
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
deleted file mode 100644
index 52b7fb6..0000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user
-
-import android.os.Bundle
-import android.view.WindowInsets.Type
-import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import dagger.Lazy
-import javax.inject.Inject
-
-/** Support a fullscreen user switcher */
-open class UserSwitcherActivity
-@Inject
-constructor(
- private val falsingCollector: FalsingCollector,
- private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
-) : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.user_switcher_fullscreen)
- window.decorView.windowInsetsController?.let { controller ->
- controller.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- controller.hide(Type.systemBars())
- }
- val viewModel =
- ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
- UserSwitcherViewBinder.bind(
- view = requireViewById(R.id.user_switcher_root),
- viewModel = viewModel,
- lifecycleOwner = this,
- layoutInflater = layoutInflater,
- falsingCollector = falsingCollector,
- onFinish = this::finish,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
new file mode 100644
index 0000000..72786ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowInsets
+import android.view.WindowInsetsController
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+
+class UserSwitchFullscreenDialog(
+ context: Context,
+ private val falsingCollector: FalsingCollector,
+ private val userSwitcherViewModel: UserSwitcherViewModel,
+) : SystemUIDialog(context, R.style.Theme_UserSwitcherFullscreenDialog) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+
+ window?.decorView?.windowInsetsController?.let { controller ->
+ controller.systemBarsBehavior =
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ controller.hide(WindowInsets.Type.systemBars())
+ }
+
+ val view =
+ LayoutInflater.from(this.context).inflate(R.layout.user_switcher_fullscreen, null)
+ setContentView(view)
+
+ UserSwitcherViewBinder.bind(
+ view = requireViewById(R.id.user_switcher_root),
+ viewModel = userSwitcherViewModel,
+ layoutInflater = layoutInflater,
+ falsingCollector = falsingCollector,
+ onFinish = this::dismiss,
+ )
+ }
+
+ override fun getWidth(): Int {
+ val displayMetrics = context.resources.displayMetrics.apply {
+ context.display.getRealMetrics(this)
+ }
+ return displayMetrics.widthPixels
+ }
+
+ override fun getHeight() = MATCH_PARENT
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 94dd1b3..0ec1a21 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -49,7 +49,6 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -513,24 +512,12 @@
}
}
- fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ fun showUserSwitcher(expandable: Expandable) {
+ if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+ } else {
showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
- return
}
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
}
private fun showDialog(request: ShowDialogRequestModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 14cc3e7..de73cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -50,4 +50,8 @@
data class ShowUserSwitcherDialog(
override val expandable: Expandable?,
) : ShowDialogRequestModel()
+
+ data class ShowUserSwitcherFullscreenDialog(
+ override val expandable: Expandable?,
+ ) : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index e137107..7236e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -31,19 +31,18 @@
import androidx.constraintlayout.helper.widget.Flow as FlowWidget
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.user.UserSwitcherPopupMenu
import com.android.systemui.user.UserSwitcherRootView
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.children
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -56,7 +55,6 @@
fun bind(
view: ViewGroup,
viewModel: UserSwitcherViewModel,
- lifecycleOwner: LifecycleOwner,
layoutInflater: LayoutInflater,
falsingCollector: FalsingCollector,
onFinish: () -> Unit,
@@ -79,88 +77,92 @@
addButton.setOnClickListener { viewModel.onOpenMenuButtonClicked() }
cancelButton.setOnClickListener { viewModel.onCancelButtonClicked() }
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- viewModel.isFinishRequested
- .filter { it }
- .collect {
- onFinish()
- viewModel.onFinished()
- }
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.isFinishRequested
+ .filter { it }
+ .collect {
+ //finish requested, we want to dismiss popupmenu at the same time
+ popupMenu?.dismiss()
+ onFinish()
+ viewModel.onFinished()
+ }
+ }
}
}
- }
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
- launch {
- viewModel.isMenuVisible.collect { isVisible ->
- if (isVisible && popupMenu?.isShowing != true) {
- popupMenu?.dismiss()
- // Use post to make sure we show the popup menu *after* the activity is
- // ready to show one to avoid a WindowManager$BadTokenException.
- view.post {
- popupMenu =
- createAndShowPopupMenu(
- context = view.context,
- anchorView = addButton,
- adapter = popupMenuAdapter,
- onDismissed = viewModel::onMenuClosed,
- )
- }
- } else if (!isVisible && popupMenu?.isShowing == true) {
- popupMenu?.dismiss()
- popupMenu = null
- }
- }
- }
-
- launch {
- viewModel.menu.collect { menuViewModels ->
- popupMenuAdapter.setItems(menuViewModels)
- }
- }
-
- launch {
- viewModel.maximumUserColumns.collect { maximumColumns ->
- flowWidget.setMaxElementsWrap(maximumColumns)
- }
- }
-
- launch {
- viewModel.users.collect { users ->
- val viewPool =
- gridContainerView.children
- .filter { it.tag == USER_VIEW_TAG }
- .toMutableList()
- viewPool.forEach {
- gridContainerView.removeView(it)
- flowWidget.removeView(it)
- }
- users.forEach { userViewModel ->
- val userView =
- if (viewPool.isNotEmpty()) {
- viewPool.removeAt(0)
- } else {
- val inflatedView =
- layoutInflater.inflate(
- R.layout.user_switcher_fullscreen_item,
- view,
- false,
+ launch {
+ viewModel.isMenuVisible.collect { isVisible ->
+ if (isVisible && popupMenu?.isShowing != true) {
+ popupMenu?.dismiss()
+ // Use post to make sure we show the popup menu *after* the activity is
+ // ready to show one to avoid a WindowManager$BadTokenException.
+ view.post {
+ popupMenu =
+ createAndShowPopupMenu(
+ context = view.context,
+ anchorView = addButton,
+ adapter = popupMenuAdapter,
+ onDismissed = viewModel::onMenuClosed,
)
- inflatedView.tag = USER_VIEW_TAG
- inflatedView
}
- userView.id = View.generateViewId()
- gridContainerView.addView(userView)
- flowWidget.addView(userView)
- UserViewBinder.bind(
- view = userView,
- viewModel = userViewModel,
- )
+ } else if (!isVisible && popupMenu?.isShowing == true) {
+ popupMenu?.dismiss()
+ popupMenu = null
+ }
+ }
+ }
+
+ launch {
+ viewModel.menu.collect { menuViewModels ->
+ popupMenuAdapter.setItems(menuViewModels)
+ }
+ }
+
+ launch {
+ viewModel.maximumUserColumns.collect { maximumColumns ->
+ flowWidget.setMaxElementsWrap(maximumColumns)
+ }
+ }
+
+ launch {
+ viewModel.users.collect { users ->
+ val viewPool =
+ gridContainerView.children
+ .filter { it.tag == USER_VIEW_TAG }
+ .toMutableList()
+ viewPool.forEach {
+ gridContainerView.removeView(it)
+ flowWidget.removeView(it)
+ }
+ users.forEach { userViewModel ->
+ val userView =
+ if (viewPool.isNotEmpty()) {
+ viewPool.removeAt(0)
+ } else {
+ val inflatedView =
+ layoutInflater.inflate(
+ R.layout.user_switcher_fullscreen_item,
+ view,
+ false,
+ )
+ inflatedView.tag = USER_VIEW_TAG
+ inflatedView
+ }
+ userView.id = View.generateViewId()
+ gridContainerView.addView(userView)
+ flowWidget.addView(userView)
+ UserViewBinder.bind(
+ view = userView,
+ viewModel = userViewModel,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 79721b3..0930cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -26,13 +26,16 @@
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.user.UserSwitchFullscreenDialog
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Provider
@@ -54,6 +57,8 @@
private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
private val eventLogger: Lazy<UiEventLogger>,
private val activityStarter: Lazy<ActivityStarter>,
+ private val falsingCollector: Lazy<FalsingCollector>,
+ private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>,
) : CoreStartable {
private var currentDialog: Dialog? = null
@@ -124,6 +129,15 @@
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog ->
+ Pair(
+ UserSwitchFullscreenDialog(
+ context = context.get(),
+ falsingCollector = falsingCollector.get(),
+ userSwitcherViewModel = userSwitcherViewModel.get(),
+ ),
+ null, /* dialogCuj */
+ )
}
currentDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 3300e8e..78edad7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -55,5 +55,5 @@
interactor.selectedUser.mapLatest { userModel -> userModel.image }
/** Action to execute on click. Should launch the user switcher */
- val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 37115ad..afd72e7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -17,12 +17,10 @@
package com.android.systemui.user.ui.viewmodel
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -36,12 +34,13 @@
import kotlinx.coroutines.flow.map
/** Models UI state for the user switcher feature. */
+@SysUISingleton
class UserSwitcherViewModel
-private constructor(
+@Inject
+constructor(
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
- private val powerInteractor: PowerInteractor,
-) : ViewModel() {
+) {
/** On-device users. */
val users: Flow<List<UserViewModel>> =
@@ -112,34 +111,15 @@
}
}
- private fun createFinishRequestedFlow(): Flow<Boolean> {
- var mostRecentSelectedUserId: Int? = null
- var mostRecentIsInteractive: Boolean? = null
-
- return combine(
- // When the user is switched, we should finish.
- userInteractor.selectedUser
- .map { it.id }
- .map {
- val selectedUserChanged =
- mostRecentSelectedUserId != null && mostRecentSelectedUserId != it
- mostRecentSelectedUserId = it
- selectedUserChanged
- },
- // When the screen turns off, we should finish.
- powerInteractor.isInteractive.map {
- val screenTurnedOff = mostRecentIsInteractive == true && !it
- mostRecentIsInteractive = it
- screenTurnedOff
- },
+ private fun createFinishRequestedFlow(): Flow<Boolean> =
+ combine(
// When the cancel button is clicked, we should finish.
hasCancelButtonBeenClicked,
// If an executed action told us to finish, we should finish,
isFinishRequiredDueToExecutedAction,
- ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
- selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
+ ) { cancelButtonClicked, executedActionFinish ->
+ cancelButtonClicked || executedActionFinish
}
- }
private fun toViewModel(
model: UserModel,
@@ -210,22 +190,4 @@
{ userInteractor.selectUser(model.id) }
}
}
-
- class Factory
- @Inject
- constructor(
- private val userInteractor: UserInteractor,
- private val guestUserInteractor: GuestUserInteractor,
- private val powerInteractor: PowerInteractor,
- ) : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST")
- return UserSwitcherViewModel(
- userInteractor = userInteractor,
- guestUserInteractor = guestUserInteractor,
- powerInteractor = powerInteractor,
- )
- as T
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 13b3b1a..082c8cc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -18,10 +18,8 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
-import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -32,7 +30,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
@@ -40,7 +37,6 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@SmallTest
@@ -80,9 +76,7 @@
Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
.thenReturn(passwordEntry)
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
- `when`(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
- .thenReturn(mock(ImageView::class.java))
- keyguardPasswordViewController =
+ keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
keyguardUpdateMonitor,
@@ -119,18 +113,6 @@
}
@Test
- fun onApplyWindowInsetsListener_onApplyWindowInsets() {
- `when`(keyguardViewController.isBouncerShowing).thenReturn(false)
- val argumentCaptor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
-
- keyguardPasswordViewController.onViewAttached()
- verify(keyguardPasswordView).setOnApplyWindowInsetsListener(argumentCaptor.capture())
- argumentCaptor.value.onApplyWindowInsets(keyguardPasswordView, null)
-
- verify(keyguardPasswordView).hideKeyboard()
- }
-
- @Test
fun testHideKeyboardWhenOnPause() {
keyguardPasswordViewController.onPause()
keyguardPasswordView.post {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4ed4e20..08813a7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -84,9 +84,11 @@
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
@@ -194,8 +196,6 @@
@Mock
private FaceManager mFaceManager;
@Mock
- private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
- @Mock
private BiometricManager mBiometricManager;
@Mock
private PackageManager mPackageManager;
@@ -254,6 +254,7 @@
@Mock
private Uri mURI;
+ private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -274,21 +275,22 @@
private StatusBarStateController.StateListener mStatusBarStateListener;
private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+ private IFingerprintAuthenticatorsRegisteredCallback
+ mFingerprintAuthenticatorsRegisteredCallback;
+ private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+
+ mFaceSensorProperties =
+ List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false));
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
- // IBiometricsFace@1.0 does not support detection, only authentication.
- when(mFaceSensorProperties.isEmpty()).thenReturn(false);
- when(mFaceSensorProperties.get(anyInt())).thenReturn(
- createFaceSensorProperties(/* supportsFaceDetection = */ false));
-
mFingerprintSensorProperties = List.of(
new FingerprintSensorPropertiesInternal(1 /* sensorId */,
FingerprintSensorProperties.STRENGTH_STRONG,
@@ -345,6 +347,20 @@
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+ ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
+ ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
+ mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
+ ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+ fingerprintCaptor.capture());
+ mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
+ mFingerprintAuthenticatorsRegisteredCallback
+ .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+
verify(mBiometricManager)
.registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
@@ -651,7 +667,7 @@
}
@Test
- public void whenDetectFace_biometricDetectCallback() {
+ public void whenDetectFace_biometricDetectCallback() throws RemoteException {
ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor =
ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class);
@@ -801,7 +817,8 @@
}
@Test
- public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
+ public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning()
+ throws RemoteException {
// GIVEN bypass is enabled, face detection is supported
lockscreenBypassIsAllowed();
supportsFaceDetection();
@@ -825,7 +842,7 @@
}
@Test
- public void faceDetect_whenStrongAuthRequiredAndBypass() {
+ public void faceDetect_whenStrongAuthRequiredAndBypass() throws RemoteException {
givenDetectFace();
// FACE detect is triggered, not authenticate
@@ -2614,6 +2631,37 @@
assertThat(captor.getValue().getWakeReason())
.isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
}
+ @Test
+ public void testFingerprintSensorProperties() throws RemoteException {
+ mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
+ new ArrayList<>());
+
+ assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
+ KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+
+ mFingerprintAuthenticatorsRegisteredCallback
+ .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+
+ verifyFingerprintAuthenticateCall();
+ assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
+ KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+ }
+ @Test
+ public void testFaceSensorProperties() throws RemoteException {
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
+
+ assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+ KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+ biometricsEnabledForCurrentUser();
+
+ verifyFaceAuthenticateNeverCalled();
+ verifyFaceDetectNeverCalled();
+ assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+ KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
@@ -2656,10 +2704,10 @@
.thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
}
- private void supportsFaceDetection() {
- when(mFaceSensorProperties.get(anyInt()))
- .thenReturn(createFaceSensorProperties(
- /* supportsFaceDetection = */ true));
+ private void supportsFaceDetection() throws RemoteException {
+ mFaceSensorProperties =
+ List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true));
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
}
private void lockscreenBypassIsAllowed() {
@@ -2868,7 +2916,7 @@
mTestableLooper.processAllMessages();
}
- private void givenDetectFace() {
+ private void givenDetectFace() throws RemoteException {
// GIVEN bypass is enabled, face detection is supported and strong auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index 1ce2572..39ea46a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -126,7 +126,7 @@
}
fun assertLastProgress(progress: Float) {
- assertThat(progressHistory.last()).isWithin(1.0E-4F).of(progress)
+ waitForCondition { progress == progressHistory.last() }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8d74c82..adba538 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,7 +19,6 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
-import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.Bitmap
@@ -49,7 +48,6 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -58,11 +56,9 @@
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.user.utils.MultiUserActionsEvent
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertNotNull
@@ -842,7 +838,7 @@
fun `show user switcher - full screen disabled - shows dialog switcher`() =
testScope.runTest {
val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
+ underTest.showUserSwitcher(expandable)
val dialogRequest = collectLastValue(underTest.dialogShowRequests)
@@ -855,30 +851,22 @@
}
@Test
- fun `show user switcher - full screen enabled - launches activity`() {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ fun `show user switcher - full screen enabled - launches full screen dialog`() =
+ testScope.runTest {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(expandable)
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
+
+ // Dialog is shown.
+ assertThat(dialogRequest())
+ .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest()).isNull()
+ }
@Test
fun `users - secondary user - managed profile is not included`() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 7780a43..a342dad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,8 +34,6 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -88,7 +86,6 @@
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -116,7 +113,6 @@
}
keyguardRepository = FakeKeyguardRepository()
- powerRepository = FakePowerRepository()
val refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
@@ -145,7 +141,7 @@
set(Flags.FACE_AUTH_REFACTOR, true)
}
underTest =
- UserSwitcherViewModel.Factory(
+ UserSwitcherViewModel(
userInteractor =
UserInteractor(
applicationContext = context,
@@ -174,13 +170,8 @@
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
),
- powerInteractor =
- PowerInteractor(
- repository = powerRepository,
- ),
guestUserInteractor = guestUserInteractor,
)
- .create(UserSwitcherViewModel::class.java)
}
@Test
@@ -327,46 +318,12 @@
}
@Test
- fun `isFinishRequested - finishes when user is switched`() =
- testScope.runTest {
- val userInfos = setUsers(count = 2)
- val isFinishRequested = mutableListOf<Boolean>()
- val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() =
- testScope.runTest {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- val isFinishRequested = mutableListOf<Boolean>()
- val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- powerRepository.setInteractive(false)
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
fun `isFinishRequested - finishes when cancel button is clicked`() =
testScope.runTest {
setUsers(count = 2)
- powerRepository.setInteractive(true)
val isFinishRequested = mutableListOf<Boolean>()
val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
assertThat(isFinishRequested.last()).isFalse()
underTest.onCancelButtonClicked()
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a60f06a..51d349f 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5086,7 +5086,6 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
try {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6bd3c79..e6ef3b4 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -999,23 +999,50 @@
private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
@NonNull BroadcastRecord oldRecord) {
- final String newMatchingKey = getDeliveryGroupMatchingKey(newRecord);
- final String oldMatchingKey = getDeliveryGroupMatchingKey(oldRecord);
final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
// If neither delivery group key nor matching filter is specified, then use
// Intent.filterEquals() to identify the delivery group.
- if (newMatchingKey == null && oldMatchingKey == null && newMatchingFilter == null) {
+ if (isMatchingKeyNull(newRecord) && isMatchingKeyNull(oldRecord)
+ && newMatchingFilter == null) {
return newRecord.intent.filterEquals(oldRecord.intent);
}
if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
return false;
}
- return Objects.equals(newMatchingKey, oldMatchingKey);
+ return areMatchingKeysEqual(newRecord, oldRecord);
+ }
+
+ private static boolean isMatchingKeyNull(@NonNull BroadcastRecord record) {
+ final String namespace = getDeliveryGroupMatchingNamespaceFragment(record);
+ final String key = getDeliveryGroupMatchingKeyFragment(record);
+ // If either namespace or key part is null, then treat the entire matching key as null.
+ return namespace == null || key == null;
+ }
+
+ private static boolean areMatchingKeysEqual(@NonNull BroadcastRecord newRecord,
+ @NonNull BroadcastRecord oldRecord) {
+ final String newNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(newRecord);
+ final String oldNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(oldRecord);
+ if (!Objects.equals(newNamespaceFragment, oldNamespaceFragment)) {
+ return false;
+ }
+
+ final String newKeyFragment = getDeliveryGroupMatchingKeyFragment(newRecord);
+ final String oldKeyFragment = getDeliveryGroupMatchingKeyFragment(oldRecord);
+ return Objects.equals(newKeyFragment, oldKeyFragment);
}
@Nullable
- private static String getDeliveryGroupMatchingKey(@NonNull BroadcastRecord record) {
- return record.options == null ? null : record.options.getDeliveryGroupMatchingKey();
+ private static String getDeliveryGroupMatchingNamespaceFragment(
+ @NonNull BroadcastRecord record) {
+ return record.options == null
+ ? null : record.options.getDeliveryGroupMatchingNamespaceFragment();
+ }
+
+ @Nullable
+ private static String getDeliveryGroupMatchingKeyFragment(@NonNull BroadcastRecord record) {
+ return record.options == null
+ ? null : record.options.getDeliveryGroupMatchingKeyFragment();
}
@Nullable
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b32e8f0..ee2e458 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3259,6 +3259,12 @@
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
return key_consumed;
+ case KeyEvent.KEYCODE_MACRO_1:
+ case KeyEvent.KEYCODE_MACRO_2:
+ case KeyEvent.KEYCODE_MACRO_3:
+ case KeyEvent.KEYCODE_MACRO_4:
+ Slog.wtf(TAG, "KEYCODE_MACRO_x should be handled in interceptKeyBeforeQueueing");
+ return key_consumed;
}
if (isValidGlobalKey(keyCode)
@@ -4396,6 +4402,13 @@
result &= ~ACTION_PASS_TO_USER;
break;
}
+ case KeyEvent.KEYCODE_MACRO_1:
+ case KeyEvent.KEYCODE_MACRO_2:
+ case KeyEvent.KEYCODE_MACRO_3:
+ case KeyEvent.KEYCODE_MACRO_4:
+ // TODO(b/266098478): Add logic to handle KEYCODE_MACROx feature
+ result &= ~ACTION_PASS_TO_USER;
+ break;
}
if (useHapticFeedback) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9069ac5..2ea59b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -173,6 +173,7 @@
import static com.android.server.wm.ActivityRecordProto.NAME;
import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS;
import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS;
+import static com.android.server.wm.ActivityRecordProto.OVERRIDE_ORIENTATION;
import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED;
import static com.android.server.wm.ActivityRecordProto.PROC_ID;
import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
@@ -10212,6 +10213,7 @@
proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds());
proto.write(ENABLE_RECENTS_SCREENSHOT, mEnableRecentsScreenshot);
proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
+ proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 38dadc6..6bfcd39 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -29,10 +29,6 @@
import android.service.credentials.CallingAppInfo;
import android.util.Log;
-import com.android.server.credentials.metrics.ApiName;
-import com.android.server.credentials.metrics.ApiStatus;
-import com.android.server.credentials.metrics.ProviderStatusForMetrics;
-
import java.util.ArrayList;
/**
@@ -40,7 +36,7 @@
* responses from providers, and updates the provider(S) state.
*/
public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
- IClearCredentialStateCallback>
+ IClearCredentialStateCallback, Void>
implements ProviderSession.ProviderInternalCallback<Void> {
private static final String TAG = "GetRequestSession";
@@ -50,7 +46,6 @@
long startedTimestamp) {
super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED,
callingAppInfo, cancellationSignal, startedTimestamp);
- setupInitialPhaseMetric(ApiName.CLEAR_CREDENTIAL.getMetricCode(), MetricUtilities.ZERO);
}
/**
@@ -92,8 +87,10 @@
public void onFinalResponseReceived(
ComponentName componentName,
Void response) {
- setChosenMetric(componentName);
- respondToClientWithResponseAndFinish();
+ mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
+ mProviders.get(componentName.flattenToString()).mProviderSessionMetric
+ .getCandidatePhasePerProviderMetric());
+ respondToClientWithResponseAndFinish(null);
}
protected void onProviderResponseComplete(ComponentName componentName) {
@@ -114,63 +111,28 @@
}
@Override
+ protected void invokeClientCallbackSuccess(Void response) throws RemoteException {
+ mClientCallback.onSuccess();
+ }
+
+ @Override
+ protected void invokeClientCallbackError(String errorType, String errorMsg)
+ throws RemoteException {
+ mClientCallback.onError(errorType, errorMsg);
+ }
+
+ @Override
public void onFinalErrorReceived(ComponentName componentName, String errorType,
String message) {
//Not applicable for clearCredential as response is not picked by the user
}
- private void respondToClientWithResponseAndFinish() {
- Log.i(TAG, "respondToClientWithResponseAndFinish");
- collectFinalPhaseMetricStatus(false, ProviderStatusForMetrics.FINAL_SUCCESS);
- if (isSessionCancelled()) {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
- finishSession(/*propagateCancellation=*/true);
- return;
- }
- try {
- mClientCallback.onSuccess();
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.SUCCESS.getMetricCode());
- } catch (RemoteException e) {
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- Log.i(TAG, "Issue while propagating the response to the client");
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
- }
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
- Log.i(TAG, "respondToClientWithErrorAndFinish");
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- if (isSessionCancelled()) {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
- finishSession(/*propagateCancellation=*/true);
- return;
- }
- try {
- mClientCallback.onError(errorType, errorMsg);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
- finishSession(/*propagateCancellation=*/false);
- }
-
private void processResponses() {
for (ProviderSession session : mProviders.values()) {
if (session.isProviderResponseSet()) {
// If even one provider responded successfully, send back the response
// TODO: Aggregate other exceptions
- respondToClientWithResponseAndFinish();
+ respondToClientWithResponseAndFinish(null);
return;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 687c861..dfd8cfa 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -35,8 +35,6 @@
import android.service.credentials.PermissionUtils;
import android.util.Log;
-import com.android.server.credentials.metrics.ApiName;
-import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -47,7 +45,7 @@
* provider(s) state maintained in {@link ProviderCreateSession}.
*/
public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
- ICreateCredentialCallback>
+ ICreateCredentialCallback, CreateCredentialResponse>
implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
private static final String TAG = "CreateRequestSession";
@@ -59,7 +57,6 @@
long startedTimestamp) {
super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE,
callingAppInfo, cancellationSignal, startedTimestamp);
- setupInitialPhaseMetric(ApiName.CREATE_CREDENTIAL.getMetricCode(), MetricUtilities.UNIT);
}
/**
@@ -85,7 +82,7 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
- mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+ mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
try {
mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
@@ -95,7 +92,7 @@
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
providerDataList));
} catch (RemoteException e) {
- mChosenProviderFinalPhaseMetric.setUiReturned(false);
+ mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
respondToClientWithErrorAndFinish(
CreateCredentialException.TYPE_UNKNOWN,
"Unable to invoke selector");
@@ -103,18 +100,31 @@
}
@Override
+ protected void invokeClientCallbackSuccess(CreateCredentialResponse response)
+ throws RemoteException {
+ mClientCallback.onResponse(response);
+ }
+
+ @Override
+ protected void invokeClientCallbackError(String errorType, String errorMsg)
+ throws RemoteException {
+ mClientCallback.onError(errorType, errorMsg);
+ }
+
+ @Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable CreateCredentialResponse response) {
- mChosenProviderFinalPhaseMetric.setUiReturned(true);
- mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
- setChosenMetric(componentName);
+ mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
+ mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
+ componentName.flattenToString()).mProviderSessionMetric
+ .getCandidatePhasePerProviderMetric());
if (response != null) {
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ mRequestSessionMetric.collectChosenProviderStatus(
ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ mRequestSessionMetric.collectChosenProviderStatus(
ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
@@ -144,75 +154,6 @@
"No create options available.");
}
- private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
- Log.i(TAG, "respondToClientWithResponseAndFinish");
- // TODO(b/271135048) - Improve Metrics super/sub class setup and emit.
- collectFinalPhaseMetricStatus(false, ProviderStatusForMetrics.FINAL_SUCCESS);
- if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
- return;
- }
- if (isSessionCancelled()) {
- // TODO(b/271135048) - Migrate to superclass utilities (post beta1 cleanup) - applies
- // for all
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
- finishSession(/*propagateCancellation=*/true);
- return;
- }
- try {
- mClientCallback.onResponse(response);
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.SUCCESS.getMetricCode());
- } catch (RemoteException e) {
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- Log.i(TAG, "Issue while responding to client: " + e.getMessage());
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
- }
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
- Log.i(TAG, "respondToClientWithErrorAndFinish");
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
- return;
- }
- if (isSessionCancelled()) {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
- finishSession(/*propagateCancellation=*/true);
- return;
- }
- try {
- mClientCallback.onError(errorType, errorMsg);
- } catch (RemoteException e) {
- Log.i(TAG, "Issue while responding to client: " + e.getMessage());
- }
- logFailureOrUserCancel(errorType);
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void logFailureOrUserCancel(String errorType) {
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
- mChosenProviderFinalPhaseMetric.setHasException(false);
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.USER_CANCELED.getMetricCode());
- } else {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
- }
- }
-
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 06da76e5..e65b9f3 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -707,9 +707,10 @@
private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
try {
- var initMetric = session.mInitialPhaseMetric;
+ var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
- MetricUtilities.logApiCalled(initMetric, ++session.mSequenceCounter);
+ MetricUtilities.logApiCalledInitialPhase(initMetric,
+ session.mRequestSessionMetric.returnIncrementSequence());
} catch (Exception e) {
Log.w(TAG, "Unexpected error during metric logging: " + e);
}
@@ -788,7 +789,7 @@
if (serviceComponentName.equals(componentName)) {
if (!s.getServicePackageName().equals(callingPackage)) {
// The component name and the package name do not match.
- MetricUtilities.logApiCalled(
+ MetricUtilities.logApiCalledSimpleV1(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
ApiStatus.FAILURE, callingUid);
Log.w(
@@ -797,7 +798,8 @@
+ " match package name.");
return false;
}
- MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+ MetricUtilities.logApiCalledSimpleV1(
+ ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
ApiStatus.SUCCESS, callingUid);
// TODO(b/271135048) - Update asap to use the new logging types
return true;
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8082cdb..93f543e 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -27,14 +27,11 @@
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
-import android.os.Binder;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.util.Log;
-import com.android.server.credentials.metrics.ApiName;
-import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -45,7 +42,7 @@
* responses from providers, and the UX app, and updates the provider(S) state.
*/
public class GetRequestSession extends RequestSession<GetCredentialRequest,
- IGetCredentialCallback>
+ IGetCredentialCallback, GetCredentialResponse>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
public GetRequestSession(Context context, int userId, int callingUid,
@@ -57,7 +54,7 @@
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
- setupInitialPhaseMetric(ApiName.GET_CREDENTIAL.getMetricCode(), numTypes);
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes);
}
/**
@@ -83,112 +80,58 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
- mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+ mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
try {
- Binder.withCleanCallingIdentity(() ->
- mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+ mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
- providerDataList)));
- } catch (RuntimeException e) {
- mChosenProviderFinalPhaseMetric.setUiReturned(false);
+ providerDataList));
+ } catch (RemoteException e) {
+ mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
respondToClientWithErrorAndFinish(
GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
}
}
@Override
+ protected void invokeClientCallbackSuccess(GetCredentialResponse response)
+ throws RemoteException {
+ mClientCallback.onResponse(response);
+ }
+
+ @Override
+ protected void invokeClientCallbackError(String errorType, String errorMsg)
+ throws RemoteException {
+ mClientCallback.onError(errorType, errorMsg);
+ }
+
+ @Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
- mChosenProviderFinalPhaseMetric.setUiReturned(true);
- mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
- setChosenMetric(componentName);
+ mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
+ mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
+ mProviders.get(componentName.flattenToString())
+ .mProviderSessionMetric.getCandidatePhasePerProviderMetric());
if (response != null) {
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ mRequestSessionMetric.collectChosenProviderStatus(
ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ mRequestSessionMetric.collectChosenProviderStatus(
ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
}
}
- //TODO: Try moving the three error & response methods below to RequestSession to be shared
- // between get & create.
+ //TODO(b/274954697): Further shorten the three below to completely migrate to superclass
@Override
public void onFinalErrorReceived(ComponentName componentName, String errorType,
String message) {
respondToClientWithErrorAndFinish(errorType, message);
}
- private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
- collectFinalPhaseMetricStatus(false, ProviderStatusForMetrics.FINAL_SUCCESS);
- if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
- return;
- }
- if (isSessionCancelled()) {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
- finishSession(/*propagateCancellation=*/true);
- return;
- }
- try {
- mClientCallback.onResponse(response);
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.SUCCESS.getMetricCode());
- } catch (RemoteException e) {
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
- }
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
- return;
- }
- if (isSessionCancelled()) {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
- finishSession(/*propagateCancellation=*/true);
- return;
- }
-
- try {
- mClientCallback.onError(errorType, errorMsg);
- } catch (RemoteException e) {
- Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
- }
- logFailureOrUserCancel(errorType);
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void logFailureOrUserCancel(String errorType) {
- collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
- if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
- mChosenProviderFinalPhaseMetric.setHasException(false);
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.USER_CANCELED.getMetricCode());
- } else {
- logApiCall(mChosenProviderFinalPhaseMetric,
- mCandidateBrowsingPhaseMetric,
- /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
- }
- }
-
@Override
public void onUiCancellation(boolean isUserCancellation) {
if (isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 65fb368..c48654a 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -37,8 +37,10 @@
* from {@link com.android.internal.util.FrameworkStatsLog}.
*/
public class MetricUtilities {
+ private static final boolean LOG_FLAG = true;
private static final String TAG = "MetricUtilities";
+ public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED";
public static final int DEFAULT_INT_32 = -1;
public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
@@ -90,10 +92,13 @@
* @param apiStatus the final status of this particular api call
* @param emitSequenceId an emitted sequence id for the current session
*/
- protected static void logApiCalled(ChosenProviderFinalPhaseMetric finalPhaseMetric,
+ public static void logApiCalledFinalPhase(ChosenProviderFinalPhaseMetric finalPhaseMetric,
List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus,
int emitSequenceId) {
try {
+ if (!LOG_FLAG) {
+ return;
+ }
int browsedSize = browsingPhaseMetrics.size();
int[] browsedClickedEntries = new int[browsedSize];
int[] browsedProviderUid = new int[browsedSize];
@@ -151,9 +156,12 @@
* @param providers a map with known providers and their held metric objects
* @param emitSequenceId an emitted sequence id for the current session
*/
- protected static void logApiCalled(Map<String, ProviderSession> providers,
+ public static void logApiCalledCandidatePhase(Map<String, ProviderSession> providers,
int emitSequenceId) {
try {
+ if (!LOG_FLAG) {
+ return;
+ }
var providerSessions = providers.values();
int providerSize = providerSessions.size();
int sessionId = -1;
@@ -171,7 +179,8 @@
int[] candidateRemoteEntryCountList = new int[providerSize];
int index = 0;
for (var session : providerSessions) {
- CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
+ CandidatePhaseMetric metric = session.mProviderSessionMetric
+ .getCandidatePhasePerProviderMetric();
if (sessionId == -1) {
sessionId = metric.getSessionId();
}
@@ -225,14 +234,18 @@
* contain default values for all other optional parameters.
*
* TODO(b/271135048) - given space requirements, this may be a good candidate for another atom
+ * TODO immediately remove and carry over TODO to new log for this setup
*
* @param apiName the api name to log
* @param apiStatus the status to log
* @param callingUid the calling uid
*/
- protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
+ public static void logApiCalledSimpleV1(ApiName apiName, ApiStatus apiStatus,
int callingUid) {
try {
+ if (!LOG_FLAG) {
+ return;
+ }
FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
/* api_name */apiName.getMetricCode(),
/* caller_uid */ callingUid,
@@ -258,8 +271,12 @@
* @param initialPhaseMetric contains all the data for this emit
* @param sequenceNum the sequence number for this api call session emit
*/
- protected static void logApiCalled(InitialPhaseMetric initialPhaseMetric, int sequenceNum) {
+ public static void logApiCalledInitialPhase(InitialPhaseMetric initialPhaseMetric,
+ int sequenceNum) {
try {
+ if (!LOG_FLAG) {
+ return;
+ }
FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE,
/* api_name */ initialPhaseMetric.getApiName(),
/* caller_uid */ initialPhaseMetric.getCallerUid(),
@@ -275,5 +292,4 @@
Log.w(TAG, "Unexpected error during metric logging: " + e);
}
}
-
}
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index f48fc2c..5c93f6b 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -38,7 +38,6 @@
import android.service.credentials.PermissionUtils;
import android.util.Log;
-import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -50,7 +49,7 @@
* responses from providers, and the UX app, and updates the provider(S) state.
*/
public class PrepareGetRequestSession extends RequestSession<GetCredentialRequest,
- IGetCredentialCallback>
+ IGetCredentialCallback, GetCredentialResponse>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
@@ -67,7 +66,7 @@
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
- setupInitialPhaseMetric(ApiName.GET_CREDENTIAL.getMetricCode(), numTypes);
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes);
mPrepareGetCredentialCallback = prepareGetCredentialCallback;
}
@@ -95,32 +94,45 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
- mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+ mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
try {
mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
providerDataList));
} catch (RemoteException e) {
- mChosenProviderFinalPhaseMetric.setUiReturned(false);
+ mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
respondToClientWithErrorAndFinish(
GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
}
}
@Override
+ protected void invokeClientCallbackSuccess(GetCredentialResponse response)
+ throws RemoteException {
+ mClientCallback.onResponse(response);
+ }
+
+ @Override
+ protected void invokeClientCallbackError(String errorType, String errorMsg)
+ throws RemoteException {
+ mClientCallback.onError(errorType, errorMsg);
+ }
+
+ @Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
- mChosenProviderFinalPhaseMetric.setUiReturned(true);
- mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
- setChosenMetric(componentName);
+ mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
+ mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
+ componentName.flattenToString()).mProviderSessionMetric
+ .getCandidatePhasePerProviderMetric());
if (response != null) {
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ mRequestSessionMetric.collectChosenProviderStatus(
ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ mRequestSessionMetric.collectChosenProviderStatus(
ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
@@ -135,66 +147,6 @@
respondToClientWithErrorAndFinish(errorType, message);
}
- private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
- if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
- return;
- }
- if (isSessionCancelled()) {
-// TODO: properly log the new api
-// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-// ApiStatus.CLIENT_CANCELED);
- finishSession(/*propagateCancellation=*/true);
- return;
- }
- try {
- mClientCallback.onResponse(response);
-// TODO: properly log the new api
-// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-// ApiStatus.SUCCESS);
- } catch (RemoteException e) {
- Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
-// TODO: properly log the new api
-// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-// ApiStatus.FAILURE);
- }
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
- if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
- return;
- }
- if (isSessionCancelled()) {
-// TODO: properly log the new api
-// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-// ApiStatus.CLIENT_CANCELED);
- finishSession(/*propagateCancellation=*/true);
- return;
- }
-
- try {
- mClientCallback.onError(errorType, errorMsg);
- } catch (RemoteException e) {
- Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
- }
- logFailureOrUserCancel(errorType);
- finishSession(/*propagateCancellation=*/false);
- }
-
- private void logFailureOrUserCancel(String errorType) {
- if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
-// TODO: properly log the new api
-// logApiCall(ApiName.GET_CREDENTIAL,
-// /* apiStatus */ ApiStatus.USER_CANCELED);
- } else {
-// TODO: properly log the new api
-// logApiCall(ApiName.GET_CREDENTIAL,
-// /* apiStatus */ ApiStatus.FAILURE);
- }
- }
-
@Override
public void onUiCancellation(boolean isUserCancellation) {
if (isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 69a642d..2e7aaa0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -91,7 +91,7 @@
if (exception instanceof ClearCredentialStateException) {
mProviderException = (ClearCredentialStateException) exception;
}
- captureCandidateFailureInMetrics();
+ mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
updateStatusAndInvokeCallback(toStatus(errorCode));
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 2ba9c22..e05eb60 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -40,8 +40,6 @@
import android.util.Pair;
import android.util.Slog;
-import com.android.server.credentials.metrics.EntryEnum;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -158,7 +156,7 @@
// Store query phase exception for aggregation with final response
mProviderException = (CreateCredentialException) exception;
}
- captureCandidateFailureInMetrics();
+ mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -179,37 +177,14 @@
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
- gatherCandidateEntryMetrics(response);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
} else {
- gatherCandidateEntryMetrics(response);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
}
}
- private void gatherCandidateEntryMetrics(BeginCreateCredentialResponse response) {
- try {
- var createEntries = response.getCreateEntries();
- int numRemoteEntry = MetricUtilities.ZERO;
- if (response.getRemoteCreateEntry() != null) {
- numRemoteEntry = MetricUtilities.UNIT;
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
- }
- int numCreateEntries =
- createEntries == null ? MetricUtilities.ZERO : createEntries.size();
- if (numCreateEntries > MetricUtilities.ZERO) {
- createEntries.forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
- }
- mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries + numRemoteEntry);
- mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
- mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCreateEntries);
- mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(MetricUtilities.UNIT);
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
- }
- }
-
@Override
@Nullable
protected CreateCredentialProviderData prepareUiData()
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 7d3c86b..b5f9e53 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -43,8 +43,6 @@
import android.util.Pair;
import android.util.Slog;
-import com.android.server.credentials.metrics.EntryEnum;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -52,7 +50,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
@@ -256,7 +253,7 @@
if (exception instanceof GetCredentialException) {
mProviderException = (GetCredentialException) exception;
}
- captureCandidateFailureInMetrics();
+ mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -502,44 +499,14 @@
addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
// Log the data.
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
+ mProviderSessionMetric.collectCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
return;
}
- gatherCandidateEntryMetrics(response);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
- private void gatherCandidateEntryMetrics(BeginGetCredentialResponse response) {
- try {
- int numCredEntries = response.getCredentialEntries().size();
- int numActionEntries = response.getActions().size();
- int numAuthEntries = response.getAuthenticationActions().size();
- int numRemoteEntry = MetricUtilities.ZERO;
- if (response.getRemoteCredentialEntry() != null) {
- numRemoteEntry = MetricUtilities.UNIT;
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
- }
- response.getCredentialEntries().forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
- response.getActions().forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
- response.getAuthenticationActions().forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
- mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
- + numActionEntries + numRemoteEntry);
- mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
- int numTypes = (response.getCredentialEntries().stream()
- .map(CredentialEntry::getType).collect(
- Collectors.toSet())).size(); // Dedupe type strings
- mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(numTypes);
- mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
- mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
- mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
- }
- }
-
/**
* When an invalid state occurs, e.g. entry mismatch or no response from provider,
* we send back a TYPE_NO_CREDENTIAL error as to the developer.
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 64ac9b3..090c076 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -31,9 +31,7 @@
import android.os.RemoteException;
import android.util.Log;
-import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.InitialPhaseMetric;
-import com.android.server.credentials.metrics.ProviderStatusForMetrics;
+import com.android.server.credentials.metrics.ProviderSessionMetric;
import java.util.UUID;
@@ -72,10 +70,8 @@
protected R mProviderResponse;
@NonNull
protected Boolean mProviderResponseSet = false;
- // Specific candidate provider metric for the provider this session handles
@NonNull
- protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
- new CandidatePhaseMetric();
+ protected final ProviderSessionMetric mProviderSessionMetric = new ProviderSessionMetric();
@NonNull
private int mProviderSessionUid;
@@ -209,49 +205,18 @@
return mRemoteCredentialService;
}
- protected void captureCandidateFailureInMetrics() {
- mCandidatePhasePerProviderMetric.setHasException(true);
- }
-
/** Updates the status . */
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
setStatus(status);
- updateCandidateMetric(status);
+ mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status),
+ isCompletionStatus(status), mProviderSessionUid);
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
- private void updateCandidateMetric(Status status) {
- try {
- mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
- mCandidatePhasePerProviderMetric
- .setQueryFinishTimeNanoseconds(System.nanoTime());
- if (isTerminatingStatus(status)) {
- mCandidatePhasePerProviderMetric.setQueryReturned(false);
- mCandidatePhasePerProviderMetric.setProviderQueryStatus(
- ProviderStatusForMetrics.QUERY_FAILURE
- .getMetricCode());
- } else if (isCompletionStatus(status)) {
- mCandidatePhasePerProviderMetric.setQueryReturned(true);
- mCandidatePhasePerProviderMetric.setProviderQueryStatus(
- ProviderStatusForMetrics.QUERY_SUCCESS
- .getMetricCode());
- }
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
- }
- }
-
- // Common method to transfer metrics from the initial phase to the candidate phase per provider
+ /** Common method that transfers metrics from the init phase to candidates */
protected void startCandidateMetrics() {
- try {
- InitialPhaseMetric initMetric = ((RequestSession) mCallbacks).mInitialPhaseMetric;
- mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
- mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
- initMetric.getCredentialServiceStartedTimeNanoseconds());
- mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
- }
+ mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric(
+ ((RequestSession) mCallbacks).mRequestSessionMetric.getInitialPhaseMetric());
}
/** Get the request to be sent to the provider. */
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index d4ad65e..cfb9ad4 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -16,8 +16,6 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.logApiCalled;
-
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.ComponentName;
@@ -30,27 +28,25 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.util.Log;
import com.android.internal.R;
-import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
-import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
-import com.android.server.credentials.metrics.EntryEnum;
-import com.android.server.credentials.metrics.InitialPhaseMetric;
+import com.android.server.credentials.metrics.ApiName;
+import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
+import com.android.server.credentials.metrics.RequestSessionMetric;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
* Base class of a request session, that listens to UI events. This class must be extended
* every time a new response type is expected from the providers.
*/
-abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback {
+abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback {
private static final String TAG = "RequestSession";
// TODO: Revise access levels of attributes
@@ -77,16 +73,7 @@
protected final CancellationSignal mCancellationSignal;
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
- protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
- protected final ChosenProviderFinalPhaseMetric
- mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
-
- // TODO(b/271135048) - Group metrics used in a scope together, such as here in RequestSession
- // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
- @NonNull
- protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
- // As emits occur in sequential order, increment this counter and utilize
- protected int mSequenceCounter = 0;
+ protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric();
protected final String mHybridService;
@NonNull
@@ -122,17 +109,8 @@
mUserId, this);
mHybridService = context.getResources().getString(
R.string.config_defaultCredentialManagerHybridService);
- initialPhaseMetricSetup(timestampStarted);
- }
-
- private void initialPhaseMetricSetup(long timestampStarted) {
- try {
- mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
- mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
- mInitialPhaseMetric.setCallerUid(mCallingUid);
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
- }
+ mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId,
+ mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
}
public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
@@ -140,11 +118,10 @@
protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
- // Sets up the initial metric collector for use across all request session impls
- protected void setupInitialPhaseMetric(int metricCode, int requestClassType) {
- this.mInitialPhaseMetric.setApiName(metricCode);
- this.mInitialPhaseMetric.setCountRequestClassType(requestClassType);
- }
+ protected abstract void invokeClientCallbackSuccess(V response) throws RemoteException;
+
+ protected abstract void invokeClientCallbackError(String errorType, String errorMsg) throws
+ RemoteException;
public void addProviderSession(ComponentName componentName, ProviderSession providerSession) {
mProviders.put(componentName.flattenToString(), providerSession);
@@ -170,26 +147,12 @@
return;
}
Log.i(TAG, "Provider session found");
- logBrowsingPhasePerSelect(selection, providerSession);
+ mRequestSessionMetric.collectMetricPerBrowsingSelect(selection,
+ providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric());
providerSession.onUiEntrySelected(selection.getEntryKey(),
selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
}
- private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
- ProviderSession providerSession) {
- try {
- CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
- browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
- browsingPhaseMetric.setEntryEnum(
- EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
- browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
- .getCandidateUid());
- this.mCandidateBrowsingPhaseMetric.add(browsingPhaseMetric);
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
- }
- }
-
protected void finishSession(boolean propagateCancellation) {
Log.i(TAG, "finishing session");
if (propagateCancellation) {
@@ -208,14 +171,6 @@
return false;
}
- protected void logApiCall(ChosenProviderFinalPhaseMetric finalPhaseMetric,
- List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus) {
- // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
- // For the returned types by authentication entries - i.e. a CandidatePhase During Browse
- // Possibly think of adding in more atoms for other APIs as well.
- logApiCalled(finalPhaseMetric, browsingPhaseMetrics, apiStatus, ++mSequenceCounter);
- }
-
protected boolean isSessionCancelled() {
return mCancellationSignal.isCanceled();
}
@@ -239,7 +194,7 @@
ArrayList<ProviderData> providerDataList = getProviderDataForUi();
if (!providerDataList.isEmpty()) {
Log.i(TAG, "provider list not empty about to initiate ui");
- MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+ mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
launchUiWithProviderData(providerDataList);
}
}
@@ -251,7 +206,7 @@
ArrayList<ProviderData> providerDataList = new ArrayList<>();
if (isSessionCancelled()) {
- MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+ mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
finishSession(/*propagateCancellation=*/true);
return providerDataList;
}
@@ -267,54 +222,65 @@
return providerDataList;
}
- protected void collectFinalPhaseMetricStatus(boolean hasException,
- ProviderStatusForMetrics finalSuccess) {
- mChosenProviderFinalPhaseMetric.setHasException(hasException);
- mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
- finalSuccess.getMetricCode());
+ /**
+ * Allows subclasses to directly finalize the call and set closing metrics on response.
+ *
+ * @param response the response associated with the API call that just completed
+ */
+ protected void respondToClientWithResponseAndFinish(V response) {
+ mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false,
+ ProviderStatusForMetrics.FINAL_SUCCESS);
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Log.i(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+ if (isSessionCancelled()) {
+ mRequestSessionMetric.logApiCalledAtFinish(
+ /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode());
+ finishSession(/*propagateCancellation=*/true);
+ return;
+ }
+ try {
+ invokeClientCallbackSuccess(response);
+ mRequestSessionMetric.logApiCalledAtFinish(
+ /*apiStatus=*/ ApiStatus.SUCCESS.getMetricCode());
+ } catch (RemoteException e) {
+ mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
+ /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
+ Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
+ mRequestSessionMetric.logApiCalledAtFinish(
+ /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode());
+ }
+ finishSession(/*propagateCancellation=*/false);
}
/**
- * Called by RequestSession's upon chosen metric determination. It's expected that most bits
- * are transferred here. However, certain new information, such as the selected provider's final
- * exception bit, the framework to ui and back latency, or the ui response bit are set at other
- * locations. Other information, such browsing metrics, api_status, and the sequence id count
- * are combined together during the final emit moment with the actual and official
- * {@link com.android.internal.util.FrameworkStatsLog} metric generation.
+ * Allows subclasses to directly finalize the call and set closing metrics on error completion.
*
- * @param componentName the componentName to associate with a provider
+ * @param errorType the type of error given back in the flow
+ * @param errorMsg the error message given back in the flow
*/
- protected void setChosenMetric(ComponentName componentName) {
- try {
- CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
- .mCandidatePhasePerProviderMetric;
-
- mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
- mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
-
- mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
- metric.getQueryLatencyMicroseconds());
-
- mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
- metric.getServiceBeganTimeNanoseconds());
- mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
- metric.getStartQueryTimeNanoseconds());
- mChosenProviderFinalPhaseMetric.setQueryEndTimeNanoseconds(metric
- .getQueryFinishTimeNanoseconds());
-
- mChosenProviderFinalPhaseMetric.setNumEntriesTotal(metric.getNumEntriesTotal());
- mChosenProviderFinalPhaseMetric.setCredentialEntryCount(metric
- .getCredentialEntryCount());
- mChosenProviderFinalPhaseMetric.setCredentialEntryTypeCount(
- metric.getCredentialEntryTypeCount());
- mChosenProviderFinalPhaseMetric.setActionEntryCount(metric.getActionEntryCount());
- mChosenProviderFinalPhaseMetric.setRemoteEntryCount(metric.getRemoteEntryCount());
- mChosenProviderFinalPhaseMetric.setAuthenticationEntryCount(
- metric.getAuthenticationEntryCount());
- mChosenProviderFinalPhaseMetric.setAvailableEntries(metric.getAvailableEntries());
- mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
+ /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Log.i(TAG, "Request has already been completed. This is strange.");
+ return;
}
+ if (isSessionCancelled()) {
+ mRequestSessionMetric.logApiCalledAtFinish(
+ /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode());
+ finishSession(/*propagateCancellation=*/true);
+ return;
+ }
+
+ try {
+ invokeClientCallbackError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
+ }
+ boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING);
+ mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled);
+ finishSession(/*propagateCancellation=*/false);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index abd749c..f40e73e 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -16,12 +16,22 @@
package com.android.server.credentials.metrics;
+import static android.credentials.ui.RequestInfo.TYPE_CREATE;
+import static android.credentials.ui.RequestInfo.TYPE_GET;
+import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED;
+
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CLEAR_CREDENTIAL;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CREATE_CREDENTIAL;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_GET_CREDENTIAL;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_UNKNOWN;
+import android.credentials.ui.RequestInfo;
+import android.util.Log;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
public enum ApiName {
UNKNOWN(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_UNKNOWN),
GET_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_GET_CREDENTIAL),
@@ -31,12 +41,24 @@
CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
);
+ private static final String TAG = "ApiName";
+
private final int mInnerMetricCode;
+ private static final Map<String, Integer> sRequestInfoToMetric = Map.ofEntries(
+ new AbstractMap.SimpleEntry<>(TYPE_CREATE,
+ CREATE_CREDENTIAL.mInnerMetricCode),
+ new AbstractMap.SimpleEntry<>(TYPE_GET,
+ GET_CREDENTIAL.mInnerMetricCode),
+ new AbstractMap.SimpleEntry<>(TYPE_UNDEFINED,
+ CLEAR_CREDENTIAL.mInnerMetricCode)
+ );
+
ApiName(int innerMetricCode) {
this.mInnerMetricCode = innerMetricCode;
}
+
/**
* Gives the West-world version of the metric name.
*
@@ -45,4 +67,20 @@
public int getMetricCode() {
return this.mInnerMetricCode;
}
+
+ /**
+ * Given a string key type known to the framework, this returns the known metric code associated
+ * with that string. This is mainly used by {@link RequestSessionMetric} collection contexts.
+ * This relies on {@link RequestInfo} string keys.
+ *
+ * @param stringKey a string key type for a particular request info
+ * @return the metric code associated with this request info's api name counterpart
+ */
+ public static int getMetricCodeFromRequestInfo(String stringKey) {
+ if (!sRequestInfoToMetric.containsKey(stringKey)) {
+ Log.w(TAG, "Attempted to use an unsupported string key request info");
+ return UNKNOWN.mInnerMetricCode;
+ }
+ return sRequestInfoToMetric.get(stringKey);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 4053294..10d4f9c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -16,12 +16,14 @@
package com.android.server.credentials.metrics;
+import android.util.IntArray;
import android.util.Log;
import com.android.server.credentials.MetricUtilities;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
/**
* The central candidate provider metric object that mimics our defined metric setup.
@@ -70,7 +72,7 @@
// The count of authentication entries from this provider, defaults to -1
private int mAuthenticationEntryCount = -1;
// Gathered to pass on to chosen provider when required
- private final List<Integer> mAvailableEntries = new ArrayList<>();
+ private final IntArray mAvailableEntries = new IntArray();
public CandidatePhaseMetric() {
}
@@ -263,6 +265,6 @@
* this metric
*/
public List<Integer> getAvailableEntries() {
- return new ArrayList<>(this.mAvailableEntries); // no alias copy
+ return Arrays.stream(mAvailableEntries.toArray()).boxed().collect(Collectors.toList());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
new file mode 100644
index 0000000..76fd478
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+import android.annotation.NonNull;
+import android.service.credentials.BeginCreateCredentialResponse;
+import android.service.credentials.BeginGetCredentialResponse;
+import android.service.credentials.CredentialEntry;
+import android.util.Log;
+
+import com.android.server.credentials.MetricUtilities;
+
+import java.util.stream.Collectors;
+
+/**
+ * Provides contextual metric collection for objects generated from
+ * {@link com.android.server.credentials.ProviderSession} flows to isolate metric
+ * collection from the core codebase. For any future additions to the ProviderSession subclass
+ * list, metric collection should be added to this file.
+ */
+public class ProviderSessionMetric {
+
+ private static final String TAG = "ProviderSessionMetric";
+
+ // Specific candidate provider metric for the provider this session handles
+ @NonNull
+ protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
+ new CandidatePhaseMetric();
+
+ public ProviderSessionMetric() {}
+
+ /**
+ * Retrieve the candidate provider phase metric and the data it contains.
+ */
+ public CandidatePhaseMetric getCandidatePhasePerProviderMetric() {
+ return mCandidatePhasePerProviderMetric;
+ }
+
+ /**
+ * This collects for ProviderSessions, with respect to the candidate providers, whether
+ * an exception occurred in the candidate call.
+ *
+ * @param hasException indicates if the candidate provider associated with an exception
+ */
+ public void collectCandidateExceptionStatus(boolean hasException) {
+ mCandidatePhasePerProviderMetric.setHasException(hasException);
+ }
+
+ /**
+ * Used to collect metrics at the update stage when a candidate provider gives back an update.
+ *
+ * @param isFailureStatus indicates the candidate provider sent back a terminated response
+ * @param isCompletionStatus indicates the candidate provider sent back a completion response
+ * @param providerSessionUid the uid of the provider
+ */
+ public void collectCandidateMetricUpdate(boolean isFailureStatus,
+ boolean isCompletionStatus, int providerSessionUid) {
+ try {
+ mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid);
+ mCandidatePhasePerProviderMetric
+ .setQueryFinishTimeNanoseconds(System.nanoTime());
+ if (isFailureStatus) {
+ mCandidatePhasePerProviderMetric.setQueryReturned(false);
+ mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+ ProviderStatusForMetrics.QUERY_FAILURE
+ .getMetricCode());
+ } else if (isCompletionStatus) {
+ mCandidatePhasePerProviderMetric.setQueryReturned(true);
+ mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+ ProviderStatusForMetrics.QUERY_SUCCESS
+ .getMetricCode());
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Starts the collection of a single provider metric in the candidate phase of the API flow.
+ * It's expected that this should be called at the start of the query phase so that session id
+ * and timestamps can be shared. They can be accessed granular-ly through the underlying
+ * objects, but for {@link com.android.server.credentials.ProviderSession} context metrics,
+ * it's recommended to use these context-specified methods.
+ *
+ * @param initMetric the pre candidate phase metric collection object of type
+ * {@link InitialPhaseMetric} used to transfer initial information
+ */
+ public void collectCandidateMetricSetupViaInitialMetric(InitialPhaseMetric initMetric) {
+ try {
+ mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
+ mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
+ initMetric.getCredentialServiceStartedTimeNanoseconds());
+ mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Once candidate providers give back entries, this helps collect their info for metric
+ * purposes.
+ *
+ * @param response contains entries and data from the candidate provider responses
+ * @param <R> the response type associated with the API flow in progress
+ */
+ public <R> void collectCandidateEntryMetrics(R response) {
+ try {
+ if (response instanceof BeginGetCredentialResponse) {
+ beginGetCredentialResponseCollectionCandidateEntryMetrics(
+ (BeginGetCredentialResponse) response);
+ } else if (response instanceof BeginCreateCredentialResponse) {
+ beginCreateCredentialResponseCollectionCandidateEntryMetrics(
+ (BeginCreateCredentialResponse) response);
+ } else {
+ Log.i(TAG, "Your response type is unsupported for metric logging");
+ }
+
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ private void beginCreateCredentialResponseCollectionCandidateEntryMetrics(
+ BeginCreateCredentialResponse response) {
+ var createEntries = response.getCreateEntries();
+ int numRemoteEntry = MetricUtilities.ZERO;
+ if (response.getRemoteCreateEntry() != null) {
+ numRemoteEntry = MetricUtilities.UNIT;
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
+ }
+ int numCreateEntries =
+ createEntries == null ? MetricUtilities.ZERO : createEntries.size();
+ if (numCreateEntries > MetricUtilities.ZERO) {
+ createEntries.forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+ }
+ mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries + numRemoteEntry);
+ mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
+ mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCreateEntries);
+ mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(MetricUtilities.UNIT);
+ }
+
+ private void beginGetCredentialResponseCollectionCandidateEntryMetrics(
+ BeginGetCredentialResponse response) {
+ int numCredEntries = response.getCredentialEntries().size();
+ int numActionEntries = response.getActions().size();
+ int numAuthEntries = response.getAuthenticationActions().size();
+ int numRemoteEntry = MetricUtilities.ZERO;
+ if (response.getRemoteCredentialEntry() != null) {
+ numRemoteEntry = MetricUtilities.UNIT;
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
+ }
+ response.getCredentialEntries().forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+ response.getActions().forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
+ response.getAuthenticationActions().forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
+ mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
+ + numActionEntries + numRemoteEntry);
+ mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
+ int numTypes = (response.getCredentialEntries().stream()
+ .map(CredentialEntry::getType).collect(
+ Collectors.toSet())).size(); // Dedupe type strings
+ mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(numTypes);
+ mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
+ mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
+ mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
new file mode 100644
index 0000000..325b7e1
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase;
+import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase;
+
+import android.annotation.NonNull;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.server.credentials.ProviderSession;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides contextual metric collection for objects generated from classes such as
+ * {@link com.android.server.credentials.GetRequestSession},
+ * {@link com.android.server.credentials.CreateRequestSession},
+ * and {@link com.android.server.credentials.ClearRequestSession} flows to isolate metric
+ * collection from the core codebase. For any future additions to the RequestSession subclass
+ * list, metric collection should be added to this file.
+ */
+public class RequestSessionMetric {
+ private static final String TAG = "RequestSessionMetric";
+
+ // As emits occur in sequential order, increment this counter and utilize
+ protected int mSequenceCounter = 0;
+
+ protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+ protected final ChosenProviderFinalPhaseMetric
+ mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
+ // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
+ @NonNull
+ protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
+
+ public RequestSessionMetric() {
+ }
+
+ /**
+ * Increments the metric emit sequence counter and returns the current state value of the
+ * sequence.
+ *
+ * @return the current state value of the metric emit sequence.
+ */
+ public int returnIncrementSequence() {
+ return ++mSequenceCounter;
+ }
+
+
+ /**
+ * @return the initial metrics associated with the request session
+ */
+ public InitialPhaseMetric getInitialPhaseMetric() {
+ return mInitialPhaseMetric;
+ }
+
+ /**
+ * Upon starting the service, this fills the initial phase metric properly.
+ *
+ * @param timestampStarted the timestamp the service begins at
+ * @param mRequestId the IBinder used to retrieve a unique id
+ * @param mCallingUid the calling process's uid
+ * @param metricCode typically pulled from {@link ApiName}
+ */
+ public void collectInitialPhaseMetricInfo(long timestampStarted, IBinder mRequestId,
+ int mCallingUid, int metricCode) {
+ try {
+ mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
+ mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
+ mInitialPhaseMetric.setCallerUid(mCallingUid);
+ mInitialPhaseMetric.setApiName(metricCode);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Collects whether the UI returned for metric purposes.
+ *
+ * @param uiReturned indicates whether the ui returns or not
+ */
+ public void collectUiReturnedFinalPhase(boolean uiReturned) {
+ try {
+ mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Sets the start time for the UI being called for metric purposes.
+ *
+ * @param uiCallStartTime the nanosecond time when the UI call began
+ */
+ public void collectUiCallStartTime(long uiCallStartTime) {
+ try {
+ mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(uiCallStartTime);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * When the UI responds to the framework at the very final phase, this collects the timestamp
+ * and status of the return for metric purposes.
+ *
+ * @param uiReturned indicates whether the ui returns or not
+ * @param uiEndTimestamp the nanosecond time when the UI call ended
+ */
+ public void collectUiResponseData(boolean uiReturned, long uiEndTimestamp) {
+ try {
+ mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
+ mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(uiEndTimestamp);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Collects the final chosen provider status, with the status value coming from
+ * {@link ApiStatus}.
+ *
+ * @param status the final status of the chosen provider
+ */
+ public void collectChosenProviderStatus(int status) {
+ try {
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(status);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Collects request class type count in the RequestSession flow.
+ *
+ * @param requestClassTypeCount the number of class types in the request
+ */
+ public void collectGetFlowInitialMetricInfo(int requestClassTypeCount) {
+ try {
+ mInitialPhaseMetric.setCountRequestClassType(requestClassTypeCount);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * During browsing, where multiple entries can be selected, this collects the browsing phase
+ * metric information.
+ * TODO(b/271135048) - modify asap to account for a new metric emit per browse response to
+ * framework.
+ *
+ * @param selection contains the selected entry key type
+ * @param selectedProviderPhaseMetric contains the utility information of the selected provider
+ */
+ public void collectMetricPerBrowsingSelect(UserSelectionDialogResult selection,
+ CandidatePhaseMetric selectedProviderPhaseMetric) {
+ try {
+ CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
+ browsingPhaseMetric.setSessionId(mInitialPhaseMetric.getSessionId());
+ browsingPhaseMetric.setEntryEnum(
+ EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
+ browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid());
+ mCandidateBrowsingPhaseMetric.add(browsingPhaseMetric);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Updates the final phase metric with the designated bit
+ *
+ * @param exceptionBitFinalPhase represents if the final phase provider had an exception
+ */
+ private void setHasExceptionFinalPhase(boolean exceptionBitFinalPhase) {
+ try {
+ mChosenProviderFinalPhaseMetric.setHasException(exceptionBitFinalPhase);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Allows encapsulating the overall final phase metric status from the chosen and final
+ * provider.
+ *
+ * @param hasException represents if the final phase provider had an exception
+ * @param finalStatus represents the final status of the chosen provider
+ */
+ public void collectFinalPhaseProviderMetricStatus(boolean hasException,
+ ProviderStatusForMetrics finalStatus) {
+ try {
+ mChosenProviderFinalPhaseMetric.setHasException(hasException);
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ finalStatus.getMetricCode());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Called by RequestSessions upon chosen metric determination. It's expected that most bits
+ * are transferred here. However, certain new information, such as the selected provider's final
+ * exception bit, the framework to ui and back latency, or the ui response bit are set at other
+ * locations. Other information, such browsing metrics, api_status, and the sequence id count
+ * are combined during the final emit moment with the actual and official
+ * {@link com.android.internal.util.FrameworkStatsLog} metric generation.
+ *
+ * @param candidatePhaseMetric the componentName to associate with a provider
+ */
+ public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) {
+ try {
+ mChosenProviderFinalPhaseMetric.setSessionId(candidatePhaseMetric.getSessionId());
+ mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid());
+
+ mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
+ candidatePhaseMetric.getQueryLatencyMicroseconds());
+
+ mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+ candidatePhaseMetric.getServiceBeganTimeNanoseconds());
+ mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+ candidatePhaseMetric.getStartQueryTimeNanoseconds());
+ mChosenProviderFinalPhaseMetric.setQueryEndTimeNanoseconds(candidatePhaseMetric
+ .getQueryFinishTimeNanoseconds());
+
+ mChosenProviderFinalPhaseMetric.setNumEntriesTotal(candidatePhaseMetric
+ .getNumEntriesTotal());
+ mChosenProviderFinalPhaseMetric.setCredentialEntryCount(candidatePhaseMetric
+ .getCredentialEntryCount());
+ mChosenProviderFinalPhaseMetric.setCredentialEntryTypeCount(
+ candidatePhaseMetric.getCredentialEntryTypeCount());
+ mChosenProviderFinalPhaseMetric.setActionEntryCount(candidatePhaseMetric
+ .getActionEntryCount());
+ mChosenProviderFinalPhaseMetric.setRemoteEntryCount(candidatePhaseMetric
+ .getRemoteEntryCount());
+ mChosenProviderFinalPhaseMetric.setAuthenticationEntryCount(
+ candidatePhaseMetric.getAuthenticationEntryCount());
+ mChosenProviderFinalPhaseMetric.setAvailableEntries(candidatePhaseMetric
+ .getAvailableEntries());
+ mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * In the final phase, this helps log use cases that were either pure failures or user
+ * canceled. It's expected that {@link #collectFinalPhaseProviderMetricStatus(boolean,
+ * ProviderStatusForMetrics) collectFinalPhaseProviderMetricStatus} is called prior to this.
+ * Otherwise, the logging will miss required bits
+ *
+ * @param isUserCanceledError a boolean indicating if the error was due to user cancelling
+ */
+ public void logFailureOrUserCancel(boolean isUserCanceledError) {
+ try {
+ if (isUserCanceledError) {
+ setHasExceptionFinalPhase(/* has_exception */ false);
+ logApiCalledAtFinish(
+ /* apiStatus */ ApiStatus.USER_CANCELED.getMetricCode());
+ } else {
+ logApiCalledAtFinish(
+ /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Handles candidate phase metric emit in the RequestSession context, after the candidate phase
+ * completes.
+ *
+ * @param providers a map with known providers and their held metric objects
+ */
+ public void logCandidatePhaseMetrics(Map<String, ProviderSession> providers) {
+ try {
+ logApiCalledCandidatePhase(providers, ++mSequenceCounter);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * Handles the final logging for RequestSession context for the final phase.
+ *
+ * @param apiStatus the final status of the api being called
+ */
+ public void logApiCalledAtFinish(int apiStatus) {
+ try {
+ // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
+ // For the returned types by authentication entries - i.e. a CandidatePhase During
+ // Browse
+ // Possibly think of adding in more atoms for other APIs as well.
+ logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric,
+ apiStatus,
+ ++mSequenceCounter);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
index 9f9e6a3..d813962 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
@@ -57,7 +57,8 @@
@Parameterized.Parameters(name = "reboot={0}")
@JvmStatic
- fun parameters() = arrayOf(false, true)
+ // TODO(b/275403538): re-enable non-reboot scenarios with better tracking of APK removal
+ fun parameters() = arrayOf(/*false, */true)
data class Volume(
val diskId: String,
@@ -200,15 +201,6 @@
// TODO: There must be a better way to prevent it from auto-mounting.
removeVirtualDisk()
device.reboot()
- } else {
- // Because PackageManager unmount scan is asynchronous, need to retry until the package
- // has been unloaded. This only has to be done in the non-reboot case. Reboot will
- // clear the data structure by its nature.
- retryUntilSuccess {
- // The compiler section will print the state of the physical APK
- HostUtils.packageSection(device, pkgName, sectionName = "Compiler stats")
- .any { it.contains("Unable to find package: $pkgName") }
- }
}
}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index 1cc7ccc..5cc3371 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -24,30 +24,45 @@
android_test_helper_app {
name: "PackageManagerTestAppStub",
manifest: "AndroidManifestVersion1.xml",
- srcs: []
+ srcs: [],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion1",
- manifest: "AndroidManifestVersion1.xml"
+ manifest: "AndroidManifestVersion1.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion2",
- manifest: "AndroidManifestVersion2.xml"
+ manifest: "AndroidManifestVersion2.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion3",
- manifest: "AndroidManifestVersion3.xml"
+ manifest: "AndroidManifestVersion3.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion4",
- manifest: "AndroidManifestVersion4.xml"
+ manifest: "AndroidManifestVersion4.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppOriginalOverride",
- manifest: "AndroidManifestOriginalOverride.xml"
+ manifest: "AndroidManifestOriginalOverride.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index d559b67..8c84014 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -70,6 +70,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
+import com.android.compatibility.common.util.CddTest;
import com.android.internal.content.InstallLocationUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -2911,6 +2912,7 @@
}
@LargeTest
+ @CddTest(requirements = {"3.1/C-0-8"})
public void testMinInstallableTargetSdkFail() throws Exception {
// Test installing a package that doesn't meet the minimum installable sdk requirement
setMinInstallableTargetSdkFeatureFlags();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 390119c..36d191b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1008,6 +1008,42 @@
dropboxEntryBroadcast2.first, expectedMergedBroadcast.first));
}
+ @Test
+ public void testDeliveryGroupPolicy_sameAction_differentMatchingCriteria() {
+ final Intent closeSystemDialogs1 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ final BroadcastOptions optionsCloseSystemDialog1 = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ final Intent closeSystemDialogs2 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+ .putExtra("reason", "testing");
+ final BroadcastOptions optionsCloseSystemDialog2 = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing");
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs1, optionsCloseSystemDialog1));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs2, optionsCloseSystemDialog2));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs1, optionsCloseSystemDialog1));
+ // Verify that only the older broadcast with no extras was removed.
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ verifyPendingRecords(queue, List.of(closeSystemDialogs2, closeSystemDialogs1));
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs2, optionsCloseSystemDialog2));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs1, optionsCloseSystemDialog1));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs2, optionsCloseSystemDialog2));
+ // Verify that only the older broadcast with no extras was removed.
+ verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2));
+ }
+
private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs,
int droppedCount) {
final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ba4a54e..d2431f1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4586,6 +4586,57 @@
"data_stall_recovery_should_skip_bool_array";
/**
+ * String array containing the list of names for service numbers provided by carriers. This key
+ * should be used with {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY}. The names provided in
+ * this array will be mapped 1:1 with the numbers provided in the {@link
+ * #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} array.
+ *
+ * <p>The data would be considered valid if and only if:
+ *
+ * <ul>
+ * <li>The number of items in both the arrays are equal
+ * <li>The data added to the {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} array is valid.
+ * See {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} for more information.
+ * </ul>
+ *
+ * <p>Example:
+ *
+ * <pre><code>
+ * <string-array name="carrier_service_name_array" num="2">
+ * <item value="Police"/>
+ * <item value="Ambulance"/>
+ * </string-array>
+ * </code></pre>
+ */
+ public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
+
+ /**
+ * String array containing the list of service numbers provided by carriers. This key should be
+ * used with {@link #KEY_CARRIER_SERVICE_NAME_STRING_ARRAY}. The numbers provided in this array
+ * will be mapped 1:1 with the names provided in the {@link
+ * #KEY_CARRIER_SERVICE_NAME_STRING_ARRAY} array.
+ *
+ * <p>The data would be considered valid if and only if:
+ *
+ * <ul>
+ * <li>The number of items in both the arrays are equal
+ * <li>The item added in this key follows a specific format. Either it should be all numbers,
+ * or "+" followed by all numbers.
+ * </ul>
+ *
+ * <p>Example:
+ *
+ * <pre><code>
+ * <string-array name="carrier_service_number_array" num="2">
+ * <item value="123"/>
+ * <item value="+343"/>
+ * </string-array>
+ * </code></pre>
+ */
+ public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY =
+ "carrier_service_number_array";
+
+ /**
* Configs used by ImsServiceEntitlement.
*/
public static final class ImsServiceEntitlement {
@@ -10285,6 +10336,8 @@
new long[] {180000, 180000, 180000, 180000});
sDefaults.putBooleanArray(KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
new boolean[] {false, false, true, false, false});
+ sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
+ sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
}
/**