Merge "Specifies RECEIVER_EXPORTED for FlashNotificationsController broadcast receiver."
diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS
new file mode 100644
index 0000000..9452ea3
--- /dev/null
+++ b/PERFORMANCE_OWNERS
@@ -0,0 +1,5 @@
+timmurray@google.com
+edgararriaga@google.com
+dualli@google.com
+carmenjackson@google.com
+philipcuadra@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a48ce0c..9610f2c 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -198,5 +198,30 @@
}
]
}
+ ],
+ "auto-features-postsubmit": [
+ // Test tag for automotive feature targets. These are only running in postsubmit.
+ // This tag is used in targeted test features testing to limit resource use.
+ // TODO(b/256932212): this tag to be removed once the above is no longer in use.
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.UserVisibilityMediatorSUSDTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserVisibilityMediatorMUMDTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserVisibilityMediatorMUPANDTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
]
}
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index c2a72b7..3103fcf 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -36,7 +36,9 @@
import android.os.HandlerExecutor;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
import android.util.Log;
@@ -90,6 +92,14 @@
public class AlarmManager {
private static final String TAG = "AlarmManager";
+ /**
+ * Prefix used by {{@link #makeTag(long, WorkSource)}} to make a tag on behalf of the caller
+ * when the {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API is
+ * used. This prefix is a unique sequence of characters to differentiate with other tags that
+ * apps may provide to other APIs that accept a listener callback.
+ */
+ private static final String GENERATED_TAG_PREFIX = "$android.alarm.generated";
+
/** @hide */
@IntDef(prefix = { "RTC", "ELAPSED" }, value = {
RTC_WAKEUP,
@@ -912,6 +922,24 @@
}
/**
+ * This is only used to make an identifying tag for the deprecated
+ * {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API which doesn't
+ * accept a tag. For all other APIs, the tag provided by the app is used, even if it is
+ * {@code null}.
+ */
+ private static String makeTag(long triggerMillis, WorkSource ws) {
+ final StringBuilder tagBuilder = new StringBuilder(GENERATED_TAG_PREFIX);
+
+ tagBuilder.append(":");
+ final int attributionUid =
+ (ws == null || ws.isEmpty()) ? Process.myUid() : ws.getAttributionUid();
+ tagBuilder.append(UserHandle.formatUid(attributionUid));
+ tagBuilder.append(":");
+ tagBuilder.append(triggerMillis);
+ return tagBuilder.toString();
+ }
+
+ /**
* Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
* Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
* <p>
@@ -937,8 +965,8 @@
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
long intervalMillis, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler,
@Nullable WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
- targetHandler, workSource, null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener,
+ makeTag(triggerAtMillis, workSource), targetHandler, workSource, null);
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index b87dec1..f18b11e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -434,7 +434,7 @@
final UidStats uidStats =
getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
- if (jobStatus.shouldTreatAsExpeditedJob() && jobStatus.shouldTreatAsUserInitiatedJob()) {
+ if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.shouldTreatAsUserInitiatedJob()) {
if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
// Don't request a direct hole through any of the firewalls. Instead, mark the
// constraint as satisfied if the network is available, and the job will get
@@ -444,7 +444,9 @@
}
// Don't need to update constraint here if the network goes away. We'll do that as part
// of regular processing when we're notified about the drop.
- } else if (jobStatus.isRequestedExpeditedJob()
+ } else if (((jobStatus.isRequestedExpeditedJob() && !jobStatus.shouldTreatAsExpeditedJob())
+ || (jobStatus.getJob().isUserInitiated()
+ && !jobStatus.shouldTreatAsUserInitiatedJob()))
&& jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
// Make sure we don't accidentally keep the constraint as satisfied if the job went
// from being expedited-ready to not-expeditable.
diff --git a/core/api/current.txt b/core/api/current.txt
index df10ffe..ed81918 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6717,6 +6717,7 @@
method public boolean areNotificationsEnabled();
method public boolean areNotificationsPaused();
method public boolean canNotifyAsPackage(@NonNull String);
+ method public boolean canSendFullScreenIntent();
method public void cancel(int);
method public void cancel(@Nullable String, int);
method public void cancelAll();
@@ -36766,6 +36767,7 @@
field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+ field public static final String ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT = "android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENT";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
@@ -40256,7 +40258,7 @@
}
public class BeginGetCredentialOption implements android.os.Parcelable {
- ctor public BeginGetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+ ctor public BeginGetCredentialOption(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
method public int describeContents();
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public String getType();
@@ -40339,8 +40341,9 @@
}
public class CredentialEntry implements android.os.Parcelable {
- ctor public CredentialEntry(@NonNull String, @NonNull android.app.slice.Slice);
+ ctor public CredentialEntry(@NonNull android.service.credentials.BeginGetCredentialOption, @NonNull android.app.slice.Slice);
method public int describeContents();
+ method @NonNull public android.service.credentials.BeginGetCredentialOption getBeginGetCredentialOption();
method @NonNull public android.app.slice.Slice getSlice();
method @NonNull public String getType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -43371,7 +43374,7 @@
field public static final String KEY_PREFIX = "imssms.";
field public static final String KEY_SMS_CSFB_RETRY_ON_FAILURE_BOOL = "imssms.sms_csfb_retry_on_failure_bool";
field public static final String KEY_SMS_MAX_RETRY_COUNT_INT = "imssms.sms_max_retry_count_int";
- field public static final String KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT = "imssms.sms_max_retry_count_over_ims_int";
+ field public static final String KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT = "imssms.sms_max_retry_over_ims_count_int";
field public static final String KEY_SMS_OVER_IMS_FORMAT_INT = "imssms.sms_over_ims_format_int";
field public static final String KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT = "imssms.sms_over_ims_send_retry_delay_millis_int";
field public static final String KEY_SMS_OVER_IMS_SUPPORTED_BOOL = "imssms.sms_over_ims_supported_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a7c2bf1..20b3a26 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3274,6 +3274,7 @@
field public static final String SAFETY_CENTER_SERVICE = "safety_center";
field public static final String SEARCH_UI_SERVICE = "search_ui";
field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
+ field public static final String SHARED_CONNECTIVITY_SERVICE = "shared_connectivity";
field public static final String SMARTSPACE_SERVICE = "smartspace";
field public static final String STATS_MANAGER = "stats";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
@@ -4895,8 +4896,8 @@
public final class VirtualNavigationTouchpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
method public int describeContents();
- method public int getHeight();
- method public int getWidth();
+ method @IntRange(from=1) public int getHeight();
+ method @IntRange(from=1) public int getWidth();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualNavigationTouchpadConfig> CREATOR;
}
@@ -8916,6 +8917,10 @@
field public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED = 4; // 0x4
}
+ public class IptvFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+ method public int getProtocolCapability();
+ }
+
public class IptvFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
method @NonNull public static android.media.tv.tuner.frontend.IptvFrontendSettings.Builder builder();
method @IntRange(from=0) public long getBitrate();
@@ -9843,7 +9848,6 @@
}
public class SharedConnectivityManager {
- ctor public SharedConnectivityManager(@NonNull android.content.Context);
method public boolean connectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
method public boolean connectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork);
method public boolean disconnectTetherNetwork();
@@ -15846,11 +15850,12 @@
}
public final class MediaQualityStatus implements android.os.Parcelable {
+ ctor public MediaQualityStatus(@NonNull String, int, int, @IntRange(from=0, to=100) int, @IntRange(from=0) int, @IntRange(from=0) long);
method public int describeContents();
method @NonNull public String getCallSessionId();
method public int getMediaSessionType();
- method public long getRtpInactivityMillis();
- method public int getRtpJitterMillis();
+ method @IntRange(from=0) public long getRtpInactivityMillis();
+ method @IntRange(from=0) public int getRtpJitterMillis();
method @IntRange(from=0, to=100) public int getRtpPacketLossRate();
method public int getTransportType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -15859,14 +15864,6 @@
field public static final int MEDIA_SESSION_TYPE_VIDEO = 2; // 0x2
}
- public static final class MediaQualityStatus.Builder {
- ctor public MediaQualityStatus.Builder(@NonNull String, int, int);
- method @NonNull public android.telephony.ims.MediaQualityStatus build();
- method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpInactivityMillis(long);
- method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpJitterMillis(int);
- method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpPacketLossRate(@IntRange(from=0, to=100) int);
- }
-
public final class MediaThreshold implements android.os.Parcelable {
method public int describeContents();
method @NonNull public long[] getThresholdsRtpInactivityTimeMillis();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6a4584b..85861c3 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1735,6 +1735,7 @@
public final class MediaCas implements java.lang.AutoCloseable {
method public void forceResourceLost();
+ method public boolean isAidlHal();
}
public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
@@ -1745,6 +1746,10 @@
method public int getMaxMacroBlocks();
}
+ public final class MediaDescrambler implements java.lang.AutoCloseable {
+ method public boolean isAidlHal();
+ }
+
public final class MediaRoute2Info implements android.os.Parcelable {
method @NonNull public String getOriginalId();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eb57b3d..8395ec6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -786,10 +786,10 @@
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
- int sendingUser, int sentFromUid, String sentFromPackage) {
+ int sendingUser, int sendingUid, String sendingPackage) {
super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
- assumeDelivered, token, sendingUser, intent.getFlags(), sentFromUid,
- sentFromPackage);
+ assumeDelivered, token, sendingUser, intent.getFlags(), sendingUid,
+ sendingPackage);
this.intent = intent;
}
@@ -1045,11 +1045,11 @@
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
- int sentFromUid, String sentFromPackage) {
+ int sendingUid, String sendingPackage) {
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
- sentFromUid, sentFromPackage);
+ sendingUid, sendingPackage);
r.info = info;
sendMessage(H.RECEIVER, r);
}
@@ -1061,12 +1061,12 @@
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky,
r.assumeDelivered, r.sendingUser, r.processState,
- r.sentFromUid, r.sentFromPackage);
+ r.sendingUid, r.sendingPackage);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync,
r.assumeDelivered, r.sendingUser, r.processState,
- r.sentFromUid, r.sentFromPackage);
+ r.sendingUid, r.sendingPackage);
}
}
}
@@ -1297,7 +1297,7 @@
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
- int sentFromUid, String sentFromPackage)
+ int sendingUid, String sendingPackage)
throws RemoteException {
updateProcessState(processState, false);
@@ -1308,16 +1308,16 @@
if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser,
- sentFromUid, sentFromPackage);
+ sendingUid, sendingPackage);
} else {
if (!assumeDelivered) {
Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
+ " and " + intent + " without mechanism to finish delivery");
}
- if (sentFromUid != Process.INVALID_UID || sentFromPackage != null) {
+ if (sendingUid != Process.INVALID_UID || sendingPackage != null) {
Log.wtf(TAG,
"scheduleRegisteredReceiver() called for " + receiver + " and " + intent
- + " from " + sentFromPackage + " (UID: " + sentFromUid
+ + " from " + sendingPackage + " (UID: " + sendingUid
+ ") without mechanism to propagate the sender's identity");
}
receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index e93ce6b..c628ec4 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -734,7 +734,22 @@
* guarantees that the format is stable across devices or Android releases.</p>
*/
public @Nullable String getDescription() {
- return mDescription;
+ final StringBuilder sb = new StringBuilder();
+
+ if (mSubReason != SUBREASON_UNKNOWN) {
+ sb.append("[");
+ sb.append(subreasonToString(mSubReason));
+ sb.append("]");
+ }
+
+ if (!TextUtils.isEmpty(mDescription)) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(mDescription);
+ }
+
+ return sb.toString();
}
/**
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index c13da0b..dd6b8b5 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1683,13 +1683,13 @@
performReceive(intent, resultCode, data, extras, ordered, sticky,
BroadcastReceiver.PendingResult.guessAssumeDelivered(
BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
- sendingUser, /*sentFromUid=*/ Process.INVALID_UID,
- /*sentFromPackage=*/ null);
+ sendingUser, /*sendingUid=*/ Process.INVALID_UID,
+ /*sendingPackage=*/ null);
}
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int sentFromUid, String sentFromPackage) {
+ int sendingUser, int sendingUid, String sendingPackage) {
final LoadedApk.ReceiverDispatcher rd;
if (intent == null) {
Log.wtf(TAG, "Null intent received");
@@ -1705,7 +1705,7 @@
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
ordered, sticky, assumeDelivered, sendingUser,
- sentFromUid, sentFromPackage);
+ sendingUid, sendingPackage);
} else if (!assumeDelivered) {
// The activity manager dispatched a broadcast to a registered
// receiver in this process, but before it could be delivered the
@@ -1746,11 +1746,11 @@
public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser,
- int sentFromUid, String sentFromPackage) {
+ int sendingUid, String sendingPackage) {
super(resultCode, resultData, resultExtras,
mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
- intent.getFlags(), sentFromUid, sentFromPackage);
+ intent.getFlags(), sendingUid, sendingPackage);
mCurIntent = intent;
}
@@ -1874,9 +1874,9 @@
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int sentFromUid, String sentFromPackage) {
+ int sendingUser, int sendingUid, String sendingPackage) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
- sticky, assumeDelivered, sendingUser, sentFromUid, sentFromPackage);
+ sticky, assumeDelivered, sendingUser, sendingUid, sendingPackage);
if (intent == null) {
Log.wtf(TAG, "Null intent received");
} else {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f6d27ad..4e94a1d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -31,6 +31,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
@@ -855,6 +856,32 @@
}
/**
+ * Returns whether the calling app can send fullscreen intents.
+ * <p>From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, apps may not have
+ * permission to use {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}. If permission
+ * is denied, notification will show up as an expanded heads up notification on lockscreen.
+ * <p> To request access, add the {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}
+ * permission to your manifest, and use
+ * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}.
+ */
+ public boolean canSendFullScreenIntent() {
+ final int result = PermissionChecker.checkPermissionForPreflight(mContext,
+ android.Manifest.permission.USE_FULL_SCREEN_INTENT,
+ mContext.getAttributionSource());
+
+ switch (result) {
+ case PermissionChecker.PERMISSION_GRANTED:
+ return true;
+ case PermissionChecker.PERMISSION_SOFT_DENIED:
+ case PermissionChecker.PERMISSION_HARD_DENIED:
+ return false;
+ default:
+ if (localLOGV) Log.v(TAG, "Unknown PermissionChecker result: " + result);
+ return false;
+ }
+ }
+
+ /**
* Creates a group container for {@link NotificationChannel} objects.
*
* This can be used to rename an existing group.
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
index 7364d0f..6916f71 100644
--- a/core/java/android/app/ReceiverInfo.aidl
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -38,8 +38,8 @@
int sendingUser;
int processState;
int resultCode;
- int sentFromUid = -1;
- String sentFromPackage;
+ int sendingUid = -1;
+ String sendingPackage;
/**
* True if this instance represents a registered receiver and false if this instance
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 031d351..0365f8c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -156,6 +156,7 @@
import android.net.vcn.VcnManager;
import android.net.wifi.WifiFrameworkInitializer;
import android.net.wifi.nl80211.WifiNl80211Manager;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
import android.nfc.NfcManager;
import android.ondevicepersonalization.OnDevicePersonalizationFrameworkInitializer;
import android.os.BatteryManager;
@@ -1572,6 +1573,13 @@
Context.GRAMMATICAL_INFLECTION_SERVICE)));
}});
+ registerService(Context.SHARED_CONNECTIVITY_SERVICE, SharedConnectivityManager.class,
+ new CachedServiceFetcher<SharedConnectivityManager>() {
+ @Override
+ public SharedConnectivityManager createService(ContextImpl ctx) {
+ return new SharedConnectivityManager(ctx);
+ }
+ });
sInitializing = true;
try {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6aa7f3f..0d330ce 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6232,6 +6232,18 @@
public static final String SATELLITE_SERVICE = "satellite";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.wifi.sharedconnectivity.app.SharedConnectivityManager} for accessing
+ * shared connectivity services.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.sharedconnectivity.app.SharedConnectivityManager
+ * @hide
+ */
+ @SystemApi
+ public static final String SHARED_CONNECTIVITY_SERVICE = "shared_connectivity";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fce9f4c..3060b7f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10952,10 +10952,13 @@
/**
* Attempt to relinquish the update ownership of the given package. Only the current
- * update owner of the given package can use this API or a SecurityException will be
- * thrown.
+ * update owner of the given package can use this API.
*
* @param targetPackage The installed package whose update owner will be changed.
+ * @throws IllegalArgumentException if the given package is invalid.
+ * @throws SecurityException if you are not the current update owner of the given package.
+ *
+ * @see PackageInstaller.SessionParams#setRequestUpdateOwnership
*/
public void relinquishUpdateOwnership(@NonNull String targetPackage) {
throw new UnsupportedOperationException(
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index c67d37e..f9db5e8 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -48,7 +48,7 @@
private final String mFlattenedRequestString;
/**
- * The entry to be used in the UI.
+ * The credential entries to be used in the UI.
*/
@NonNull
private final List<CredentialEntry> mCredentialEntries;
@@ -128,16 +128,25 @@
dest.writeTypedList(mCredentialEntries, flags);
}
+ /**
+ * Returns the type of the Credential described.
+ */
@NonNull
public String getType() {
return mType;
}
+ /**
+ * Returns the flattened JSON string that will be matched with requests.
+ */
@NonNull
public String getFlattenedRequestString() {
return mFlattenedRequestString;
}
+ /**
+ * Returns the credential entries to be used in the UI.
+ */
@NonNull
public List<CredentialEntry> getCredentialEntries() {
return mCredentialEntries;
@@ -151,6 +160,7 @@
@Override
public boolean equals(Object obj) {
return Objects.equals(mType, ((CredentialDescription) obj).getType())
- && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj).getType());
+ && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj)
+ .getFlattenedRequestString());
}
}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index f106ed5..c44d1dd 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -38,7 +38,7 @@
private final List<Entry> mCredentialEntries;
@NonNull
private final List<Entry> mActionChips;
- @Nullable
+ @NonNull
private final List<Entry> mAuthenticationEntries;
@Nullable
private final Entry mRemoteEntry;
@@ -89,7 +89,7 @@
List<Entry> authenticationEntries = new ArrayList<>();
in.readTypedList(authenticationEntries, Entry.CREATOR);
- mAuthenticationEntries = actionChips;
+ mAuthenticationEntries = authenticationEntries;
AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -133,7 +133,7 @@
@NonNull private String mProviderFlattenedComponentName;
@NonNull private List<Entry> mCredentialEntries = new ArrayList<>();
@NonNull private List<Entry> mActionChips = new ArrayList<>();
- @Nullable private List<Entry> mAuthenticationEntries = new ArrayList<>();
+ @NonNull private List<Entry> mAuthenticationEntries = new ArrayList<>();
@Nullable private Entry mRemoteEntry = null;
/** Constructor with required properties. */
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 6c3233c..fa678fc 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -188,6 +188,19 @@
}
/**
+ * Shows a default subtitle for the prompt if the subtitle would otherwise be
+ * null or empty. Currently for internal use only.
+ * @return This builder.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ @NonNull
+ public Builder setUseDefaultSubtitle() {
+ mPromptInfo.setUseDefaultSubtitle(true);
+ return this;
+ }
+
+ /**
* Optional: Sets a description that will be shown on the prompt.
* @param description The description to display.
* @return This builder.
@@ -629,6 +642,16 @@
}
/**
+ * Whether to use a default subtitle. For internal use only.
+ * @return See {@link Builder#setUseDefaultSubtitle()}.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public boolean shouldUseDefaultSubtitle() {
+ return mPromptInfo.isUseDefaultSubtitle();
+ }
+
+ /**
* Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}.
* @return The description for the prompt, or null if the prompt has no description.
*/
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index a6b8096..02aad1d 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -33,6 +33,7 @@
@NonNull private CharSequence mTitle;
private boolean mUseDefaultTitle;
@Nullable private CharSequence mSubtitle;
+ private boolean mUseDefaultSubtitle;
@Nullable private CharSequence mDescription;
@Nullable private CharSequence mDeviceCredentialTitle;
@Nullable private CharSequence mDeviceCredentialSubtitle;
@@ -56,6 +57,7 @@
mTitle = in.readCharSequence();
mUseDefaultTitle = in.readBoolean();
mSubtitle = in.readCharSequence();
+ mUseDefaultSubtitle = in.readBoolean();
mDescription = in.readCharSequence();
mDeviceCredentialTitle = in.readCharSequence();
mDeviceCredentialSubtitle = in.readCharSequence();
@@ -94,6 +96,7 @@
dest.writeCharSequence(mTitle);
dest.writeBoolean(mUseDefaultTitle);
dest.writeCharSequence(mSubtitle);
+ dest.writeBoolean(mUseDefaultSubtitle);
dest.writeCharSequence(mDescription);
dest.writeCharSequence(mDeviceCredentialTitle);
dest.writeCharSequence(mDeviceCredentialSubtitle);
@@ -128,6 +131,8 @@
return true;
} else if (mUseDefaultTitle) {
return true;
+ } else if (mUseDefaultSubtitle) {
+ return true;
} else if (mDeviceCredentialTitle != null) {
return true;
} else if (mDeviceCredentialSubtitle != null) {
@@ -154,6 +159,10 @@
mSubtitle = subtitle;
}
+ public void setUseDefaultSubtitle(boolean useDefaultSubtitle) {
+ mUseDefaultSubtitle = useDefaultSubtitle;
+ }
+
public void setDescription(CharSequence description) {
mDescription = description;
}
@@ -227,6 +236,10 @@
return mSubtitle;
}
+ public boolean isUseDefaultSubtitle() {
+ return mUseDefaultSubtitle;
+ }
+
public CharSequence getDescription() {
return mDescription;
}
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index c9fc722..e9df553 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -124,7 +124,7 @@
*
* <p>Note that if 2 surfaces share the same stream via {@link
* OutputConfiguration#enableSurfaceSharing} and {@link OutputConfiguration#addSurface},
- * prepare() only needs to be called on one surface, and {link
+ * prepare() only needs to be called on one surface, and {@link
* StateCallback#onSurfacePrepared} will be triggered for both surfaces.</p>
*
* <p>{@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
index 5ad5fd9..8935efa 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -49,12 +49,12 @@
}
/** Returns the touchpad height. */
- public int getHeight() {
+ @IntRange(from = 1) public int getHeight() {
return mHeight;
}
/** Returns the touchpad width. */
- public int getWidth() {
+ @IntRange(from = 1) public int getWidth() {
return mWidth;
}
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index dcf0026..6f9c9dd 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -22,6 +22,7 @@
import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -37,6 +38,10 @@
import com.android.internal.util.Preconditions;
import com.android.server.vcn.util.PersistableBundleUtils;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -54,6 +59,17 @@
public final class VcnConfig implements Parcelable {
@NonNull private static final String TAG = VcnConfig.class.getSimpleName();
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"TRANSPORT_"},
+ value = {
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ NetworkCapabilities.TRANSPORT_WIFI,
+ })
+ @Target({ElementType.TYPE_USE})
+ public @interface VcnUnderlyingNetworkTransport {}
+
private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
static {
@@ -164,7 +180,7 @@
* @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
*/
@NonNull
- public Set<Integer> getRestrictedUnderlyingNetworkTransports() {
+ public Set<@VcnUnderlyingNetworkTransport Integer> getRestrictedUnderlyingNetworkTransports() {
return Collections.unmodifiableSet(mRestrictedTransports);
}
@@ -308,16 +324,20 @@
/**
* Sets transports that will be restricted by the VCN.
*
- * @param transports transports that will be restricted by VCN. Networks that include any
- * of the transports will be marked as restricted. Only {@link
- * NetworkCapabilities#TRANSPORT_WIFI} and {@link
- * NetworkCapabilities#TRANSPORT_CELLULAR} are allowed. {@link
+ * <p>In general, apps will not be able to bind to, or use a restricted network. In other
+ * words, unless the network type is marked restricted, any app can opt to use underlying
+ * networks, instead of through the VCN.
+ *
+ * @param transports transports that will be restricted by VCN. Networks that include any of
+ * the transports will be marked as restricted. {@link
* NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
* @return this {@link Builder} instance, for chaining
* @throws IllegalArgumentException if the input contains unsupported transport types.
+ * @see NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED
*/
@NonNull
- public Builder setRestrictedUnderlyingNetworkTransports(@NonNull Set<Integer> transports) {
+ public Builder setRestrictedUnderlyingNetworkTransports(
+ @NonNull Set<@VcnUnderlyingNetworkTransport Integer> transports) {
validateRestrictedTransportsOrThrow(transports);
mRestrictedTransports.clear();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6829cd7..cf68572 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19368,6 +19368,18 @@
"android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
/**
+ * Activity Action: Show screen for controlling whether an app can send full screen intents.
+ * <p>
+ * Input: the intent's data URI must specify the application package name for which you want
+ * to manage full screen intents.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT =
+ "android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENT";
+
+ /**
* Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
* in Settings app on large screen devices.
*
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
index 65d63c3..1df908a 100644
--- a/core/java/android/service/credentials/BeginGetCredentialOption.java
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -39,6 +39,11 @@
*/
@SuppressLint("ParcelNotFinal")
public class BeginGetCredentialOption implements Parcelable {
+ /**
+ * A unique id associated with this request option.
+ */
+ @NonNull
+ private final String mId;
/**
* The requested credential type.
@@ -53,6 +58,18 @@
private final Bundle mCandidateQueryData;
/**
+ * Returns the unique id associated with this request. Providers must pass this id
+ * to the constructor of {@link CredentialEntry} while creating a candidate credential
+ * entry for this request option.
+ *
+ * @hide
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ /**
* Returns the requested credential type.
*/
@NonNull
@@ -80,6 +97,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
dest.writeBundle(mCandidateQueryData);
+ dest.writeString8(mId);
}
@Override
@@ -92,20 +110,22 @@
return "GetCredentialOption {"
+ "type=" + mType
+ ", candidateQueryData=" + mCandidateQueryData
+ + ", id=" + mId
+ "}";
}
/**
* Constructs a {@link BeginGetCredentialOption}.
*
- * @param type the requested credential type
+ * @param id the unique id associated with this option
+ * @param type the requested credential type
* @param candidateQueryData the request candidateQueryData
- *
* @throws IllegalArgumentException If type is empty.
*/
public BeginGetCredentialOption(
- @NonNull String type,
+ @NonNull String id, @NonNull String type,
@NonNull Bundle candidateQueryData) {
+ mId = id;
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCandidateQueryData = requireNonNull(
candidateQueryData, "candidateQueryData must not be null");
@@ -114,11 +134,13 @@
private BeginGetCredentialOption(@NonNull Parcel in) {
String type = in.readString8();
Bundle candidateQueryData = in.readBundle();
+ String id = in.readString8();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
+ mId = id;
}
public static final @NonNull Creator<BeginGetCredentialOption> CREATOR =
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index b037268..b6c13c4 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -16,7 +16,10 @@
package android.service.credentials;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.slice.Slice;
@@ -29,9 +32,11 @@
* user.
*
* <p>If user selects this entry, the corresponding {@link PendingIntent},
- * set on the {@code slice} as a {@link androidx.slice.core.SliceAction} will be
- * invoked to launch activities that require some user engagement before getting
- * the credential corresponding to this entry, e.g. authentication, confirmation etc.
+ * set on the {@code slice} will be invoked to launch activities that require some user engagement
+ * before getting the credential corresponding to this entry, e.g. authentication,
+ * confirmation etc. The extras associated with the resulting {@link android.app.Activity} will
+ * also contain the complete credential request containing all required parameters. This request
+ * can be retrieved against {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_REQUEST}.
*
* Once the activity fulfills the required user engagement, the {@link android.app.Activity}
* result should be set to {@link android.app.Activity#RESULT_OK}, and the
@@ -42,24 +47,69 @@
* object passed into the constructor. Any other field will not be parceled through. If the
* derived class has custom parceling implementation, this class will not be able to unpack
* the parcel without having access to that implementation.
+ *
+ * <p>While creating this entry, providers must set a {@code requestId} to be retrieved
+ * from {@link BeginGetCredentialOption#getId()}, to determine for which request this entry is
+ * being presented to the user. This will ensure that when user selects the entry, the correct
+ * complete request is added to the {@link PendingIntent} mentioned above.
*/
@SuppressLint("ParcelNotFinal")
public class CredentialEntry implements Parcelable {
+ /** The request option that corresponds to this entry. **/
+ private final @Nullable BeginGetCredentialOption mBeginGetCredentialOption;
+
/** The type of the credential entry to be shown on the UI. */
private final @NonNull String mType;
+
/** The object containing display content to be shown along with this credential entry
* on the UI. */
private final @NonNull Slice mSlice;
+ /**
+ * Creates an entry that is associated with a {@link BeginGetCredentialOption} request.
+ * Providers must use this constructor when they extend from {@link CredentialProviderService}
+ * to respond to query phase {@link CredentialProviderService#onBeginGetCredential}
+ * credential retrieval requests.
+ *
+ * @param beginGetCredentialOption the request option for which this credential entry is
+ * being constructed This helps maintain an association,
+ * such that when the user selects this entry, providers
+ * can receive the conmplete corresponding request.
+ * @param slice the slice containing the metadata to be shown on the UI. Must be
+ * constructed through the androidx.credentials jetpack library.
+ */
+ public CredentialEntry(@NonNull BeginGetCredentialOption beginGetCredentialOption,
+ @NonNull Slice slice) {
+ mBeginGetCredentialOption = requireNonNull(beginGetCredentialOption,
+ "beginGetCredentialOption must not be null");
+ mType = requireNonNull(mBeginGetCredentialOption.getType(),
+ "type must not be null");
+ mSlice = requireNonNull(slice, "slice must not be null");
+ }
+
+ /**
+ * Creates an entry that is independent of an incoming {@link BeginGetCredentialOption}
+ * request. Providers must use this constructor for constructing entries to be registered
+ * with the framework outside of the span of an API call.
+ *
+ * @param type the type of the credential
+ * @param slice the slice containing the metadata to be shown on the UI. Must be
+ * constructed through the androidx.credentials jetpack library.
+ *
+ * @hide
+ */
+ // TODO: Unhide this constructor when the registry APIs are stable
public CredentialEntry(@NonNull String type, @NonNull Slice slice) {
- mType = type;
- mSlice = slice;
+ mBeginGetCredentialOption = null;
+ mType = requireNonNull(type, "type must not be null");
+ mSlice = requireNonNull(slice, "slice must not be null");
}
private CredentialEntry(@NonNull Parcel in) {
mType = in.readString8();
mSlice = in.readTypedObject(Slice.CREATOR);
+ mBeginGetCredentialOption = in.readTypedObject(BeginGetCredentialOption.CREATOR);
}
@NonNull
@@ -85,6 +135,15 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
dest.writeTypedObject(mSlice, flags);
+ dest.writeTypedObject(mBeginGetCredentialOption, flags);
+ }
+
+ /**
+ * Returns the request option for which this credential entry has been constructed.
+ */
+ @NonNull
+ public BeginGetCredentialOption getBeginGetCredentialOption() {
+ return mBeginGetCredentialOption;
}
/**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b98deba..ba9847e 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -76,6 +76,16 @@
public static final String SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME =
"settings_app_allow_dark_theme_activation_at_bedtime";
+ /**
+ * Flag to decouple bluetooth LE Audio Broadcast from Unicast
+ * If the flag is true, the broadcast feature will be enabled when the phone
+ * is connected to the BLE device.
+ * If the flag is false, it is not necessary to connect the BLE device.
+ * @hide
+ */
+ public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST =
+ "settings_need_connected_ble_device_for_broadcast";
+
/** @hide */
public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
@@ -199,6 +209,7 @@
DEFAULT_FLAGS.put(SETTINGS_VOLUME_PANEL_IN_SYSTEMUI, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false");
@@ -207,13 +218,13 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
- DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
+ DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "false");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_ALERTS, "false");
- DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "false");
+ DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "false");
}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3619c7b..685bd9a 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -277,8 +277,7 @@
inflater.inflate(R.menu.language_selection_list, menu);
final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
- if (mLocalePickerCollector.hasSpecificPackageName()
- && mOnActionExpandListener != null) {
+ if (mOnActionExpandListener != null) {
searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b63041b..1875ecf 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1942,9 +1942,9 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- // If we have a session send it the volume command, otherwise
- // use the suggested stream.
- if (mMediaController != null) {
+ // If we have a session and no active phone call send it the volume command,
+ // otherwise use the suggested stream.
+ if (mMediaController != null && !isActivePhoneCallKnown()) {
getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
mMediaController.getSessionToken());
} else {
@@ -1995,6 +1995,18 @@
return false;
}
+ private boolean isActivePhoneCallKnown() {
+ boolean isActivePhoneCallKnown = false;
+ AudioManager audioManager =
+ (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ int audioManagerMode = audioManager.getMode();
+ if (audioManagerMode == AudioManager.MODE_IN_CALL
+ || audioManagerMode == AudioManager.MODE_IN_COMMUNICATION) {
+ isActivePhoneCallKnown = true;
+ }
+ return isActivePhoneCallKnown;
+ }
+
private KeyguardManager getKeyguardManager() {
if (mKeyguardManager == null) {
mKeyguardManager = (KeyguardManager) getContext().getSystemService(
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3e30469..a1de6bb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6266,6 +6266,9 @@
<string-array name="config_healthConnectMigrationKnownSigners">
</string-array>
+ <!-- Package name of Health Connect data migrator application. -->
+ <string name="config_healthConnectMigratorPackageName"></string>
+
<!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
(@see https://universalstylus.org/).
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 38f1e28..da113cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -370,13 +370,7 @@
@Override
public void onBackCancelled() {
- // End the fade in animation.
mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
- mEnteringProgressSpring.cancel();
- mLeavingProgressSpring.cancel();
- // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
- mProgressAnimator.reset();
- finishAnimation();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 56aa742..81e118a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -122,9 +122,9 @@
* Start a pair of intents using legacy transition system.
*/
oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
- in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 18;
+ in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
+ in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
/**
* Start a pair of intents in one transition.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7cb5cf2..36da4cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -583,9 +583,10 @@
}
private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2,
- @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -605,9 +606,9 @@
Toast.LENGTH_SHORT).show();
}
}
- mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
- pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
- instanceId);
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
+ shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
@Override
@@ -1037,13 +1038,14 @@
@Override
public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
(controller) ->
- controller.startIntentsWithLegacyTransition(
- pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ controller.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2, splitPosition,
splitRatio, adapter, instanceId)
);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5a9170b..2a4bae9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -692,9 +692,11 @@
/** Starts a pair of intents using legacy transition. */
void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
@@ -703,15 +705,23 @@
activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
options1 = activityOptions.toBundle();
addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
mSyncQueue.queue(wct);
return;
}
addActivityOptions(options1, mSideStage);
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
- splitRatio, adapter, instanceId);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@@ -763,18 +773,19 @@
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
- mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId);
}
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
- null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
- instanceId);
+ null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
+ splitRatio, adapter, instanceId);
}
/**
@@ -784,8 +795,9 @@
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (!isSplitScreenVisible()) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
@@ -809,15 +821,19 @@
mMainStage.activate(wct, false /* reparent */);
}
- if (mainOptions == null) mainOptions = new Bundle();
- addActivityOptions(mainOptions, mMainStage);
- mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions);
+ if (options == null) options = new Bundle();
+ addActivityOptions(options, mMainStage);
+ options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
- if (mainTaskId == INVALID_TASK_ID) {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+
+ // TODO(b/268008375): Merge APIs to start a split pair into one.
+ if (mainTaskId != INVALID_TASK_ID) {
+ wct.startTask(mainTaskId, options);
+ } else if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
} else {
- wct.startTask(mainTaskId, mainOptions);
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
}
wct.reorder(mRootTaskInfo.token, true);
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index dea6097..b0cdb05 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1275,7 +1275,10 @@
|| (preset == MediaRecorder.AudioSource.VOICE_UPLINK)
|| (preset == MediaRecorder.AudioSource.VOICE_CALL)
|| (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)
- || (preset == MediaRecorder.AudioSource.ULTRASOUND)) {
+ || (preset == MediaRecorder.AudioSource.ULTRASOUND)
+ // AUDIO_SOURCE_INVALID is used by convention on default initialized
+ // audio attributes
+ || (preset == MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID)) {
mSource = preset;
} else {
setCapturePreset(preset);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 0704da4f..5bc8c04 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -944,10 +944,11 @@
}
/**
- * Check if the HAL is an AIDL implementation
+ * Check if the HAL is an AIDL implementation. For CTS testing purpose.
*
* @hide
*/
+ @TestApi
public boolean isAidlHal() {
return mICas != null;
}
diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java
index 15dee85..3c276d6 100644
--- a/media/java/android/media/MediaDescrambler.java
+++ b/media/java/android/media/MediaDescrambler.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.hardware.cas.IDescrambler;
import android.hardware.cas.ScramblingControl;
import android.hardware.cas.V1_0.IDescramblerBase;
@@ -221,10 +222,11 @@
}
/**
- * Check if the underlying HAL is AIDL. Used only for CTS.
+ * Check if the underlying HAL is AIDL. For CTS testing purpose.
*
* @hide
*/
+ @TestApi
public boolean isAidlHal() {
return mIsAidlHal;
}
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendCapabilities.java
new file mode 100644
index 0000000..b04fe17
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendCapabilities.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * IPTV Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class IptvFrontendCapabilities extends FrontendCapabilities {
+ private final int mProtocolCap;
+
+ // Used by native code
+ private IptvFrontendCapabilities(int protocolCap) {
+ mProtocolCap = protocolCap;
+ }
+
+ /**
+ * Gets the protocols of IPTV transmission (UDP/RTP) defined in
+ * {@link IptvFrontendSettings}.
+ */
+ public int getProtocolCapability() {
+ return mProtocolCap;
+ }
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index f56e236..7f4c03b 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1617,6 +1617,15 @@
interleaveModeCap, codeRateCap, bandwidthCap);
}
+jobject JTuner::getIptvFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IptvFrontendCapabilities");
+ jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(I)V");
+
+ jint protocolCap = caps.get<FrontendCapabilities::Tag::iptvCaps>()->protocolCap;
+
+ return env->NewObject(clazz, capsInit, protocolCap);
+}
+
jobject JTuner::getFrontendInfo(int id) {
shared_ptr<FrontendInfo> feInfo;
feInfo = sTunerClient->getFrontendInfo(id);
@@ -1695,6 +1704,11 @@
jcaps = getDtmbFrontendCaps(env, caps);
}
break;
+ case FrontendType::IPTV:
+ if (FrontendCapabilities::Tag::iptvCaps == caps.getTag()) {
+ jcaps = getIptvFrontendCaps(env, caps);
+ }
+ break;
default:
break;
}
@@ -3518,17 +3532,18 @@
static FrontendIptvSettingsFec getIptvFrontendSettingsFec(JNIEnv *env, const jobject &settings) {
jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IptvFrontendSettings");
- jobject fec = env->GetObjectField(settings, env->GetFieldID(clazz, "mFec",
- "[Landroid/media/tv/tuner/frontend/IptvFrontendSettingsFec;"));
jclass fecClazz = env->FindClass("android/media/tv/tuner/frontend/IptvFrontendSettingsFec");
- FrontendIptvSettingsFecType fecType =
+ jobject fec = env->GetObjectField(settings, env->GetFieldID(clazz, "mFec",
+ "Landroid/media/tv/tuner/frontend/IptvFrontendSettingsFec;"));
+
+ FrontendIptvSettingsFecType type =
static_cast<FrontendIptvSettingsFecType>(
- env->GetIntField(fec, env->GetFieldID(fecClazz, "mFec", "I")));
+ env->GetIntField(fec, env->GetFieldID(fecClazz, "mFecType", "I")));
int32_t fecColNum = env->GetIntField(fec, env->GetFieldID(fecClazz, "mFecColNum", "I"));
int32_t fecRowNum = env->GetIntField(fec, env->GetFieldID(fecClazz, "mFecRowNum", "I"));
- FrontendIptvSettingsFec frontendIptvSettingsFec {
- .type = fecType,
+ FrontendIptvSettingsFec frontendIptvSettingsFec = {
+ .type = type,
.fecColNum = fecColNum,
.fecRowNum = fecRowNum,
};
@@ -3538,31 +3553,36 @@
static FrontendSettings getIptvFrontendSettings(JNIEnv *env, const jobject &settings) {
FrontendSettings frontendSettings;
- const char *clazzName = "android/media/tv/tuner/frontend/IptvFrontendSettings";
- jclass clazz = env->FindClass(clazzName);
+ const char *className = "android/media/tv/tuner/frontend/IptvFrontendSettings";
+ jclass clazz = env->FindClass(className);
FrontendIptvSettingsProtocol protocol =
static_cast<FrontendIptvSettingsProtocol>(
env->GetIntField(settings, env->GetFieldID(clazz, "mProtocol", "I")));
FrontendIptvSettingsIgmp igmp =
static_cast<FrontendIptvSettingsIgmp>(
env->GetIntField(settings, env->GetFieldID(clazz, "mIgmp", "I")));
- FrontendIptvSettingsFec fec = getIptvFrontendSettingsFec(env, settings);
int64_t bitrate = env->GetIntField(settings, env->GetFieldID(clazz, "mBitrate", "J"));
- jstring contentUrlJString = (jstring) env->GetObjectField(settings, env->GetFieldID(
- clazz, "mContentUrl",
- "[Landroid/media/tv/tuner/frontend/IptvFrontendSettings;"));
- const char *contentUrl = env->GetStringUTFChars(contentUrlJString, 0);
- DemuxIpAddress ipAddr = getDemuxIpAddress(env, settings, clazzName);
+ jstring jContentUrl = (jstring) env->GetObjectField(settings, env->GetFieldID(
+ clazz, "mContentUrl", "Ljava/lang/String;"));
+ const char *contentUrl = env->GetStringUTFChars(jContentUrl, 0);
+ DemuxIpAddress ipAddr = getDemuxIpAddress(env, settings, className);
FrontendIptvSettings frontendIptvSettings{
.protocol = protocol,
- .fec = fec,
.igmp = igmp,
.bitrate = bitrate,
.ipAddr = ipAddr,
.contentUrl = contentUrl,
};
+
+ jobject jFec = env->GetObjectField(settings, env->GetFieldID(clazz, "mFec",
+ "Landroid/media/tv/tuner/frontend/IptvFrontendSettingsFec;"));
+ if (jFec != nullptr) {
+ frontendIptvSettings.fec = getIptvFrontendSettingsFec(env, settings);
+ }
+
frontendSettings.set<FrontendSettings::Tag::iptv>(frontendIptvSettings);
+ env->ReleaseStringUTFChars(jContentUrl, contentUrl);
return frontendSettings;
}
@@ -3879,7 +3899,6 @@
return tuner->openLnbByName(name);
}
-
static jobject android_media_tv_Tuner_open_filter(
JNIEnv *env, jobject thiz, jint type, jint subType, jlong bufferSize) {
sp<JTuner> tuner = getTuner(env, thiz);
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 4069aaf..2bb14f6 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -236,6 +236,7 @@
static jobject getIsdbsFrontendCaps(JNIEnv* env, FrontendCapabilities& caps);
static jobject getIsdbtFrontendCaps(JNIEnv* env, FrontendCapabilities& caps);
static jobject getDtmbFrontendCaps(JNIEnv* env, FrontendCapabilities& caps);
+ static jobject getIptvFrontendCaps(JNIEnv* env, FrontendCapabilities& caps);
};
class C2DataIdInfo : public C2Param {
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 5a4d256..499d130 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -32,7 +32,6 @@
android:supportsRtl="true"
android:theme="@style/Theme.CredentialSelector">
- <!--TODO: make sure implementing singleTop on NewIntent-->
<activity
android:name=".CredentialSelectorActivity"
android:exported="true"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 8c50271..51dc233 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -49,7 +49,12 @@
import java.time.Instant
-// Consider repo per screen, similar to view model?
+/**
+ * Client for interacting with Credential Manager. Also holds data inputs from it.
+ *
+ * IMPORTANT: instantiation of the object can fail if the data inputs aren't valid. Callers need
+ * to be equipped to handle this gracefully.
+ */
class CredentialManagerRepo(
private val context: Context,
intent: Intent,
@@ -81,7 +86,6 @@
GetCredentialProviderData::class.java
) ?: testGetCredentialProviderList()
else -> {
- // TODO: fail gracefully
throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
}
}
@@ -167,9 +171,9 @@
)
}
+ // IMPORTANT: new invocation should be mindful that this method can throw.
private fun getCredentialInitialUiState(): GetCredentialUiState? {
val providerEnabledList = GetFlowUtils.toProviderList(
- // TODO: handle runtime cast error
providerEnabledList as List<GetCredentialProviderData>, context
)
val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context)
@@ -179,9 +183,9 @@
)
}
+ // IMPORTANT: new invocation should be mindful that this method can throw.
private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
- // Handle runtime cast error
providerEnabledList as List<CreateCredentialProviderData>, context
)
return providerEnabledList
@@ -266,7 +270,7 @@
return listOf(
GetCredentialProviderData.Builder("io.enpass.app")
.setCredentialEntries(
- listOf<Entry>(
+ listOf(
GetTestUtils.newPasswordEntry(
context, "key1", "subkey-1", "elisa.family@outlook.com", null,
Instant.ofEpochSecond(8000L)
@@ -285,9 +289,12 @@
),
)
).setAuthenticationEntries(
- listOf<Entry>(
- GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1"),
- )
+ listOf(
+ GetTestUtils.newAuthenticationEntry(
+ context, "key2", "subkey-1", "locked-user1@gmail.com"),
+ GetTestUtils.newAuthenticationEntry(
+ context, "key2", "subkey-2", "locked-user2@gmail.com"),
+ )
).setActionChips(
listOf(
GetTestUtils.newActionEntry(
@@ -315,9 +322,8 @@
),
)
).setAuthenticationEntries(
- listOf<Entry>(
- GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1"),
- )
+ listOf(GetTestUtils.newAuthenticationEntry(
+ context, "key2", "subkey-1", "foo@email.com"))
).setActionChips(
listOf(
GetTestUtils.newActionEntry(
@@ -388,7 +394,6 @@
CreateCredentialRequest(
"androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
credentialData,
- // TODO: populate with actual data
/*candidateQueryData=*/ Bundle(),
/*isSystemProviderRequired=*/ false
),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0802afe..bf69ef4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -27,6 +27,7 @@
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -43,17 +44,6 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
- init(intent)
- }
-
- override fun onNewIntent(intent: Intent) {
- super.onNewIntent(intent)
- setIntent(intent)
- Log.d(Constants.LOG_TAG, "Existing activity received new intent")
- init(intent)
- }
-
- fun init(intent: Intent) {
try {
val userConfigRepo = UserConfigRepo(this)
val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
@@ -70,6 +60,20 @@
}
}
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ Log.d(Constants.LOG_TAG, "Existing activity received new intent")
+ try {
+ val userConfigRepo = UserConfigRepo(this)
+ val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+ val viewModel: CredentialSelectorViewModel by viewModels()
+ viewModel.onNewCredentialManagerRepo(credManRepo)
+ } catch (e: Exception) {
+ onInitializationError(e, intent)
+ }
+ }
+
@ExperimentalMaterialApi
@Composable
fun CredentialManagerBottomSheet(
@@ -88,13 +92,20 @@
handleDialogState(viewModel.uiState.dialogState)
}
- if (viewModel.uiState.createCredentialUiState != null) {
+ val createCredentialUiState = viewModel.uiState.createCredentialUiState
+ val getCredentialUiState = viewModel.uiState.getCredentialUiState
+ if (createCredentialUiState != null) {
CreateCredentialScreen(
viewModel = viewModel,
+ createCredentialUiState = createCredentialUiState,
providerActivityLauncher = launcher
)
- } else if (viewModel.uiState.getCredentialUiState != null) {
- GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
+ } else if (getCredentialUiState != null) {
+ GetCredentialScreen(
+ viewModel = viewModel,
+ getCredentialUiState = getCredentialUiState,
+ providerActivityLauncher = launcher
+ )
} else {
Log.d(Constants.LOG_TAG, "UI wasn't able to render neither get nor create flow")
reportInstantiationErrorAndFinishActivity(credManRepo)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 47ea53b..30b4b86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -104,7 +104,9 @@
entry.providerId, entry.entryKey, entry.entrySubkey,
resultCode, resultData,
)
- uiState = uiState.copy(dialogState = DialogState.COMPLETE)
+ if (entry.shouldTerminateUiUponSuccessfulProviderResult) {
+ uiState = uiState.copy(dialogState = DialogState.COMPLETE)
+ }
} else {
Log.w(Constants.LOG_TAG,
"Illegal state: received a provider result but found no matching entry.")
@@ -253,14 +255,14 @@
}
fun createFlowOnEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
+ val providerId = activeEntry.activeProvider.id
+ createFlowOnDefaultChanged(providerId)
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
activeEntry = activeEntry
)
)
- val providerId = uiState.createCredentialUiState?.activeEntry?.activeProvider?.id
- createFlowOnDefaultChanged(providerId)
}
fun createFlowOnDisabledProvidersSelected() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 167b956..ae87d95 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -17,6 +17,7 @@
package com.android.credentialmanager
import android.app.slice.Slice
+import android.app.slice.SliceItem
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
@@ -176,7 +177,9 @@
}
- /* From service data structure to UI credential entry list representation. */
+ /**
+ * Note: caller required handle empty list due to parsing error.
+ */
private fun getCredentialOptionInfoList(
providerId: String,
credentialEntries: List<Entry>,
@@ -255,6 +258,9 @@
}
}
+ /**
+ * Note: caller required handle empty list due to parsing error.
+ */
private fun getAuthenticationEntryList(
providerId: String,
providerDisplayName: String,
@@ -262,16 +268,24 @@
authEntryList: List<Entry>,
): List<AuthenticationEntryInfo> {
val result: MutableList<AuthenticationEntryInfo> = mutableListOf()
- authEntryList.forEach {
+ authEntryList.forEach { entry ->
val structuredAuthEntry =
- AuthenticationAction.fromSlice(it.slice) ?: return@forEach
+ AuthenticationAction.fromSlice(entry.slice) ?: return@forEach
+
+ // TODO: replace with official jetpack code.
+ val titleItem: SliceItem? = entry.slice.items.firstOrNull {
+ it.hasHint(
+ "androidx.credentials.provider.authenticationAction.SLICE_HINT_TITLE")
+ }
+ val title: String = titleItem?.text?.toString() ?: providerDisplayName
+
result.add(AuthenticationEntryInfo(
providerId = providerId,
- entryKey = it.key,
- entrySubkey = it.subkey,
+ entryKey = entry.key,
+ entrySubkey = entry.subkey,
pendingIntent = structuredAuthEntry.pendingIntent,
- fillInIntent = it.frameworkExtrasIntent,
- title = providerDisplayName,
+ fillInIntent = entry.frameworkExtrasIntent,
+ title = title,
icon = providerIcon,
))
}
@@ -279,7 +293,6 @@
}
private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
- // TODO: should also call fromSlice after getting the official jetpack code.
if (remoteEntry == null) {
return null
}
@@ -294,6 +307,9 @@
)
}
+ /**
+ * Note: caller required handle empty list due to parsing error.
+ */
private fun getActionEntryList(
providerId: String,
actionEntries: List<Entry>,
@@ -321,7 +337,9 @@
class CreateFlowUtils {
companion object {
- // Returns the list (potentially empty) of enabled provider.
+ /**
+ * Note: caller required handle empty list due to parsing error.
+ */
fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
context: Context,
@@ -346,7 +364,9 @@
return providerList
}
- // Returns the list (potentially empty) of disabled provider.
+ /**
+ * Note: caller required handle empty list due to parsing error.
+ */
fun toDisabledProviderList(
providerDataList: List<DisabledProviderData>?,
context: Context,
@@ -532,6 +552,9 @@
} else null
}
+ /**
+ * Note: caller required handle empty list due to parsing error.
+ */
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
index e3bbaeb..a580c6c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -23,10 +23,11 @@
import android.content.Intent
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
import android.net.Uri
import android.provider.Settings
import androidx.credentials.provider.CreateEntry
+import androidx.credentials.provider.PasswordCredentialEntry
+import androidx.credentials.provider.PublicKeyCredentialEntry
import java.time.Instant
@@ -37,6 +38,7 @@
context: Context,
key: String,
subkey: String,
+ title: String,
): Entry {
val slice = Slice.Builder(
Uri.EMPTY, SliceSpec("AuthenticationAction", 0)
@@ -52,6 +54,11 @@
.build(),
/*subType=*/null
)
+ slice.addText(
+ title,
+ null,
+ listOf("androidx.credentials.provider.authenticationAction.SLICE_HINT_TITLE")
+ )
return Entry(
key,
subkey,
@@ -94,23 +101,6 @@
)
}
- private const val SLICE_HINT_TYPE_DISPLAY_NAME =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
- private const val SLICE_HINT_TITLE =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_USER_NAME"
- private const val SLICE_HINT_SUBTITLE =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
- private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
- private const val SLICE_HINT_ICON =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PROFILE_ICON"
- private const val SLICE_HINT_PENDING_INTENT =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PENDING_INTENT"
- private const val SLICE_HINT_AUTO_ALLOWED =
- "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_AUTO_ALLOWED"
- private const val AUTO_SELECT_TRUE_STRING = "true"
- private const val AUTO_SELECT_FALSE_STRING = "false"
-
internal fun newPasswordEntry(
context: Context,
key: String,
@@ -125,90 +115,13 @@
val pendingIntent = PendingIntent.getActivity(
context, 1,
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_ONE_SHOT)
+ or PendingIntent.FLAG_ONE_SHOT)
)
- return Entry(
- key,
- subkey,
- toPasswordSlice(userName, userDisplayName, pendingIntent, lastUsedTime),
- Intent()
- )
+ val passwordEntry = PasswordCredentialEntry.Builder(context, userName, pendingIntent)
+ .setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime).build()
+ return Entry(key, subkey, passwordEntry.slice, Intent())
}
- private fun toPasswordSlice(
- title: CharSequence,
- subTitle: CharSequence?,
- pendingIntent: PendingIntent,
- lastUsedTime: Instant?,
- icon: Icon? = null,
- isAutoSelectAllowed: Boolean = true
- ): Slice {
- val type = TYPE_PASSWORD_CREDENTIAL
- val autoSelectAllowed = if (isAutoSelectAllowed) {
- AUTO_SELECT_TRUE_STRING
- } else {
- AUTO_SELECT_FALSE_STRING
- }
- val sliceBuilder = Slice.Builder(
- Uri.EMPTY, SliceSpec(
- type, 1
- )
- )
- .addText(
- "Password", /*subType=*/null,
- listOf(SLICE_HINT_TYPE_DISPLAY_NAME)
- )
- .addText(
- title, /*subType=*/null,
- listOf(SLICE_HINT_TITLE)
- )
- .addText(
- subTitle, /*subType=*/null,
- listOf(SLICE_HINT_SUBTITLE)
- )
- .addText(
- autoSelectAllowed, /*subType=*/null,
- listOf(SLICE_HINT_AUTO_ALLOWED)
- )
- if (lastUsedTime != null) {
- sliceBuilder.addLong(
- lastUsedTime.toEpochMilli(),
- /*subType=*/null,
- listOf(SLICE_HINT_LAST_USED_TIME_MILLIS)
- )
- }
- if (icon != null) {
- sliceBuilder.addIcon(
- icon, /*subType=*/null,
- listOf(SLICE_HINT_ICON)
- )
- }
- sliceBuilder.addAction(
- pendingIntent,
- Slice.Builder(sliceBuilder)
- .addHints(listOf(SLICE_HINT_PENDING_INTENT))
- .build(),
- /*subType=*/null
- )
- return sliceBuilder.build()
- }
-
-
- private const val PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
- private const val PASSKEY_SLICE_HINT_TITLE =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_USER_NAME"
- private const val PASSKEY_SLICE_HINT_SUBTITLE =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
- private const val PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
- private const val PASSKEY_SLICE_HINT_ICON =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PROFILE_ICON"
- private const val PASSKEY_SLICE_HINT_PENDING_INTENT =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PENDING_INTENT"
- private const val PASSKEY_SLICE_HINT_AUTO_ALLOWED =
- "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_AUTO_ALLOWED"
-
internal fun newPasskeyEntry(
context: Context,
key: String,
@@ -223,72 +136,11 @@
val pendingIntent = PendingIntent.getActivity(
context, 1,
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_ONE_SHOT)
+ or PendingIntent.FLAG_ONE_SHOT)
)
- return Entry(
- key, subkey, toPasskeySlice(
- userName, userDisplayName, pendingIntent, lastUsedTime
- ),
- Intent()
- )
- }
-
- private fun toPasskeySlice(
- title: CharSequence,
- subTitle: CharSequence?,
- pendingIntent: PendingIntent,
- lastUsedTime: Instant?,
- icon: Icon? = null,
- isAutoSelectAllowed: Boolean = true
- ): Slice {
- val type = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
- val autoSelectAllowed = if (isAutoSelectAllowed) {
- AUTO_SELECT_TRUE_STRING
- } else {
- AUTO_SELECT_FALSE_STRING
- }
- val sliceBuilder = Slice.Builder(
- Uri.EMPTY, SliceSpec(
- type, 1
- )
- )
- .addText(
- "Passkey", /*subType=*/null,
- listOf(PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME)
- )
- .addText(
- title, /*subType=*/null,
- listOf(PASSKEY_SLICE_HINT_TITLE)
- )
- .addText(
- subTitle, /*subType=*/null,
- listOf(PASSKEY_SLICE_HINT_SUBTITLE)
- )
- .addText(
- autoSelectAllowed, /*subType=*/null,
- listOf(PASSKEY_SLICE_HINT_AUTO_ALLOWED)
- )
- if (lastUsedTime != null) {
- sliceBuilder.addLong(
- lastUsedTime.toEpochMilli(),
- /*subType=*/null,
- listOf(PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS)
- )
- }
- if (icon != null) {
- sliceBuilder.addIcon(
- icon, /*subType=*/null,
- listOf(PASSKEY_SLICE_HINT_ICON)
- )
- }
- sliceBuilder.addAction(
- pendingIntent,
- Slice.Builder(sliceBuilder)
- .addHints(listOf(PASSKEY_SLICE_HINT_PENDING_INTENT))
- .build(),
- /*subType=*/null
- )
- return sliceBuilder.build()
+ val passkeyEntry = PublicKeyCredentialEntry.Builder(context, userName, pendingIntent)
+ .setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime).build()
+ return Entry(key, subkey, passkeyEntry.slice, Intent())
}
}
}
@@ -326,7 +178,7 @@
val pendingIntent = PendingIntent.getActivity(
context, 1,
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_ONE_SHOT)
+ or PendingIntent.FLAG_ONE_SHOT)
)
val credCountMap = mutableMapOf<String, Int>()
passwordCount?.let { credCountMap.put(TYPE_PASSWORD_CREDENTIAL, it) }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
index 4b8bc97..ee36989 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
@@ -25,4 +25,5 @@
val entrySubkey: String,
val pendingIntent: PendingIntent?,
val fillInIntent: Intent?,
+ val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index adb5467..558c229 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -62,9 +62,9 @@
@Composable
fun CreateCredentialScreen(
viewModel: CredentialSelectorViewModel,
+ createCredentialUiState: CreateCredentialUiState,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val createCredentialUiState = viewModel.uiState.createCredentialUiState ?: return
ModalBottomSheet(
sheetContent = {
// Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim
@@ -94,6 +94,7 @@
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
providerInfo = createCredentialUiState.activeEntry?.activeProvider!!,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
createOptionInfo =
createCredentialUiState.activeEntry.activeEntryInfo
as CreateOptionInfo,
@@ -264,7 +265,6 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
@@ -351,7 +351,6 @@
thickness = 24.dp,
color = Color.Transparent
)
- // TODO: handle the error situation that if multiple remoteInfos exists
enabledProviderList.forEach { enabledProvider ->
if (enabledProvider.remoteEntry != null) {
Row(
@@ -363,6 +362,7 @@
onMoreOptionsSelected
)
}
+ return@forEach
}
}
Divider(
@@ -463,7 +463,6 @@
)
}
}
- // TODO: handle the error situation that if multiple remoteInfos exists
enabledProviderList.forEach {
if (it.remoteEntry != null) {
item {
@@ -472,6 +471,7 @@
onRemoteEntrySelected = onRemoteEntrySelected,
)
}
+ return@forEach
}
}
}
@@ -549,6 +549,7 @@
onOptionSelected: (BaseEntry) -> Unit,
onConfirm: () -> Unit,
onMoreOptionsSelected: () -> Unit,
+ hasDefaultProvider: Boolean,
) {
ContainerCard() {
Column() {
@@ -601,7 +602,6 @@
onOptionSelected = onOptionSelected
)
}
- var shouldShowMoreOptionsButton = false
var createOptionsSize = 0
var remoteEntry: RemoteInfo? = null
enabledProviderList.forEach { enabledProvider ->
@@ -610,8 +610,13 @@
}
createOptionsSize += enabledProvider.createOptions.size
}
- if (createOptionsSize > 1 || remoteEntry != null) {
- shouldShowMoreOptionsButton = true
+ val shouldShowMoreOptionsButton = if (!hasDefaultProvider) {
+ // User has already been presented with all options on the default provider
+ // selection screen. Don't show them again. Therefore, only show the more option
+ // button if remote option is present.
+ remoteEntry != null
+ } else {
+ createOptionsSize > 1 || remoteEntry != null
}
Row(
horizontalArrangement =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 919411e..8d20564 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -69,7 +69,14 @@
val totalCredentialCount: Int?,
val lastUsedTime: Instant?,
val footerDescription: String?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+) : BaseEntry(
+ providerId,
+ entryKey,
+ entrySubkey,
+ pendingIntent,
+ fillInIntent,
+ shouldTerminateUiUponSuccessfulProviderResult = true,
+)
class RemoteInfo(
providerId: String,
@@ -77,7 +84,14 @@
entrySubkey: String,
pendingIntent: PendingIntent?,
fillInIntent: Intent?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+) : BaseEntry(
+ providerId,
+ entryKey,
+ entrySubkey,
+ pendingIntent,
+ fillInIntent,
+ shouldTerminateUiUponSuccessfulProviderResult = true,
+)
data class RequestDisplayInfo(
val title: String,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 8b311fe..48ee287 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -76,9 +76,9 @@
@Composable
fun GetCredentialScreen(
viewModel: CredentialSelectorViewModel,
+ getCredentialUiState: GetCredentialUiState,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val getCredentialUiState = viewModel.uiState.getCredentialUiState ?: return
if (getCredentialUiState.currentScreenState != GetScreenState.REMOTE_ONLY) {
ModalBottomSheet(
sheetContent = {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 5ab933a..bca06c7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -35,78 +35,105 @@
)
data class ProviderInfo(
- /**
- * Unique id (component name) of this provider.
- * Not for display purpose - [displayName] should be used for ui rendering.
- */
- val id: String,
- val icon: Drawable,
- val displayName: String,
- val credentialEntryList: List<CredentialEntryInfo>,
- val authenticationEntryList: List<AuthenticationEntryInfo>,
- val remoteEntry: RemoteEntryInfo?,
- val actionEntryList: List<ActionEntryInfo>,
+ /**
+ * Unique id (component name) of this provider.
+ * Not for display purpose - [displayName] should be used for ui rendering.
+ */
+ val id: String,
+ val icon: Drawable,
+ val displayName: String,
+ val credentialEntryList: List<CredentialEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ val remoteEntry: RemoteEntryInfo?,
+ val actionEntryList: List<ActionEntryInfo>,
)
/** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping
* by the provider id but instead focuses on structures convenient for display purposes. */
data class ProviderDisplayInfo(
- /**
- * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
- * Note that the list order matters to the display order.
- */
- val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
- val authenticationEntryList: List<AuthenticationEntryInfo>,
- val remoteEntry: RemoteEntryInfo?
+ /**
+ * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
+ * Note that the list order matters to the display order.
+ */
+ val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ val remoteEntry: RemoteEntryInfo?
)
class CredentialEntryInfo(
- providerId: String,
- entryKey: String,
- entrySubkey: String,
- pendingIntent: PendingIntent?,
- fillInIntent: Intent?,
- /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
- val credentialType: CredentialType,
- /** Localized type value of this credential used for display purpose. */
- val credentialTypeDisplayName: String,
- val userName: String,
- val displayName: String?,
- val icon: Drawable?,
- val lastUsedTimeMillis: Instant?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ pendingIntent: PendingIntent?,
+ fillInIntent: Intent?,
+ /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
+ val credentialType: CredentialType,
+ /** Localized type value of this credential used for display purpose. */
+ val credentialTypeDisplayName: String,
+ val userName: String,
+ val displayName: String?,
+ val icon: Drawable?,
+ val lastUsedTimeMillis: Instant?,
+) : BaseEntry(
+ providerId,
+ entryKey,
+ entrySubkey,
+ pendingIntent,
+ fillInIntent,
+ shouldTerminateUiUponSuccessfulProviderResult = true,
+)
class AuthenticationEntryInfo(
- providerId: String,
- entryKey: String,
- entrySubkey: String,
- pendingIntent: PendingIntent?,
- fillInIntent: Intent?,
- val title: String,
- val icon: Drawable,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ pendingIntent: PendingIntent?,
+ fillInIntent: Intent?,
+ val title: String,
+ val icon: Drawable,
+) : BaseEntry(
+ providerId,
+ entryKey, entrySubkey,
+ pendingIntent,
+ fillInIntent,
+ shouldTerminateUiUponSuccessfulProviderResult = false,
+)
class RemoteEntryInfo(
- providerId: String,
- entryKey: String,
- entrySubkey: String,
- pendingIntent: PendingIntent?,
- fillInIntent: Intent?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ pendingIntent: PendingIntent?,
+ fillInIntent: Intent?,
+) : BaseEntry(
+ providerId,
+ entryKey,
+ entrySubkey,
+ pendingIntent,
+ fillInIntent,
+ shouldTerminateUiUponSuccessfulProviderResult = true,
+)
class ActionEntryInfo(
- providerId: String,
- entryKey: String,
- entrySubkey: String,
- pendingIntent: PendingIntent?,
- fillInIntent: Intent?,
- val title: String,
- val icon: Drawable,
- val subTitle: String?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ pendingIntent: PendingIntent?,
+ fillInIntent: Intent?,
+ val title: String,
+ val icon: Drawable,
+ val subTitle: String?,
+) : BaseEntry(
+ providerId,
+ entryKey,
+ entrySubkey,
+ pendingIntent,
+ fillInIntent,
+ shouldTerminateUiUponSuccessfulProviderResult = false,
+)
data class RequestDisplayInfo(
- val appName: String,
+ val appName: String,
)
/**
@@ -115,18 +142,20 @@
* by last used timestamps and then by credential types
*/
data class PerUserNameCredentialEntryList(
- val userName: String,
- val sortedCredentialEntryList: List<CredentialEntryInfo>,
+ val userName: String,
+ val sortedCredentialEntryList: List<CredentialEntryInfo>,
)
/** The name of the current screen. */
enum class GetScreenState {
- /** The primary credential selection page. */
- PRIMARY_SELECTION,
- /** The secondary credential selection page, where all sign-in options are listed. */
- ALL_SIGN_IN_OPTIONS,
- /** The snackbar only page when there's no account but only a remoteEntry. */
- REMOTE_ONLY,
+ /** The primary credential selection page. */
+ PRIMARY_SELECTION,
+
+ /** The secondary credential selection page, where all sign-in options are listed. */
+ ALL_SIGN_IN_OPTIONS,
+
+ /** The snackbar only page when there's no account but only a remoteEntry. */
+ REMOTE_ONLY,
}
// IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
@@ -135,113 +164,113 @@
providerInfoList: List<ProviderInfo>
): ProviderDisplayInfo {
- val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
- val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
- val remoteEntryList = mutableListOf<RemoteEntryInfo>()
- providerInfoList.forEach { providerInfo ->
- authenticationEntryList.addAll(providerInfo.authenticationEntryList)
- if (providerInfo.remoteEntry != null) {
- remoteEntryList.add(providerInfo.remoteEntry)
- }
- // There can only be at most one remote entry
- Preconditions.checkState(remoteEntryList.size <= 1)
-
- providerInfo.credentialEntryList.forEach {
- userNameToCredentialEntryMap.compute(
- it.userName
- ) { _, v ->
- if (v == null) {
- mutableListOf(it)
- } else {
- v.add(it)
- v
+ val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
+ val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
+ val remoteEntryList = mutableListOf<RemoteEntryInfo>()
+ providerInfoList.forEach { providerInfo ->
+ authenticationEntryList.addAll(providerInfo.authenticationEntryList)
+ if (providerInfo.remoteEntry != null) {
+ remoteEntryList.add(providerInfo.remoteEntry)
}
- }
+ // There can only be at most one remote entry
+ Preconditions.checkState(remoteEntryList.size <= 1)
+
+ providerInfo.credentialEntryList.forEach {
+ userNameToCredentialEntryMap.compute(
+ it.userName
+ ) { _, v ->
+ if (v == null) {
+ mutableListOf(it)
+ } else {
+ v.add(it)
+ v
+ }
+ }
+ }
}
- }
- // Compose sortedUserNameToCredentialEntryList
- val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
- // Sort per username
- userNameToCredentialEntryMap.values.forEach {
- it.sortWith(comparator)
- }
- // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
- val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
- PerUserNameCredentialEntryList(it.key, it.value)
- }.sortedWith(
- compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
- )
+ // Compose sortedUserNameToCredentialEntryList
+ val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
+ // Sort per username
+ userNameToCredentialEntryMap.values.forEach {
+ it.sortWith(comparator)
+ }
+ // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+ val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
+ PerUserNameCredentialEntryList(it.key, it.value)
+ }.sortedWith(
+ compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
+ )
- return ProviderDisplayInfo(
- sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList,
- authenticationEntryList = authenticationEntryList,
- remoteEntry = remoteEntryList.getOrNull(0),
- )
+ return ProviderDisplayInfo(
+ sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList,
+ authenticationEntryList = authenticationEntryList,
+ remoteEntry = remoteEntryList.getOrNull(0),
+ )
}
private fun toActiveEntry(
providerDisplayInfo: ProviderDisplayInfo,
): BaseEntry? {
- val sortedUserNameToCredentialEntryList =
- providerDisplayInfo.sortedUserNameToCredentialEntryList
- val authenticationEntryList = providerDisplayInfo.authenticationEntryList
- var activeEntry: BaseEntry? = null
- if (sortedUserNameToCredentialEntryList
- .size == 1 && authenticationEntryList.isEmpty()
- ) {
- activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
- } else if (
- sortedUserNameToCredentialEntryList
- .isEmpty() && authenticationEntryList.size == 1
- ) {
- activeEntry = authenticationEntryList.first()
- }
- return activeEntry
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+ var activeEntry: BaseEntry? = null
+ if (sortedUserNameToCredentialEntryList
+ .size == 1 && authenticationEntryList.isEmpty()
+ ) {
+ activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
+ } else if (
+ sortedUserNameToCredentialEntryList
+ .isEmpty() && authenticationEntryList.size == 1
+ ) {
+ activeEntry = authenticationEntryList.first()
+ }
+ return activeEntry
}
private fun toGetScreenState(
providerInfoList: List<ProviderInfo>
): GetScreenState {
- var noLocalAccount = true
- var remoteInfo: RemoteEntryInfo? = null
- providerInfoList.forEach { providerInfo ->
- if (providerInfo.credentialEntryList.isNotEmpty() ||
- providerInfo.authenticationEntryList.isNotEmpty()) {
- noLocalAccount = false
+ var noLocalAccount = true
+ var remoteInfo: RemoteEntryInfo? = null
+ providerInfoList.forEach { providerInfo ->
+ if (providerInfo.credentialEntryList.isNotEmpty() ||
+ providerInfo.authenticationEntryList.isNotEmpty()) {
+ noLocalAccount = false
+ }
+ if (providerInfo.remoteEntry != null) {
+ remoteInfo = providerInfo.remoteEntry
+ }
}
- if (providerInfo.remoteEntry != null) {
- remoteInfo = providerInfo.remoteEntry
- }
- }
- return if (noLocalAccount && remoteInfo != null)
- GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
+ return if (noLocalAccount && remoteInfo != null)
+ GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
}
internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {
- override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
- // First prefer passkey type for its security benefits
- if (p0.credentialType != p1.credentialType) {
- if (CredentialType.PASSKEY == p0.credentialType) {
- return -1
- } else if (CredentialType.PASSKEY == p1.credentialType) {
- return 1
- }
- }
+ override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
+ // First prefer passkey type for its security benefits
+ if (p0.credentialType != p1.credentialType) {
+ if (CredentialType.PASSKEY == p0.credentialType) {
+ return -1
+ } else if (CredentialType.PASSKEY == p1.credentialType) {
+ return 1
+ }
+ }
- // Then order by last used timestamp
- if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
- if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
- return 1
- } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
- return -1
- }
- } else if (p0.lastUsedTimeMillis != null) {
- return -1
- } else if (p1.lastUsedTimeMillis != null) {
- return 1
+ // Then order by last used timestamp
+ if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
+ if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
+ return 1
+ } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
+ return -1
+ }
+ } else if (p0.lastUsedTimeMillis != null) {
+ return -1
+ } else if (p1.lastUsedTimeMillis != null) {
+ return 1
+ }
+ return 0
}
- return 0
- }
}
\ No newline at end of file
diff --git a/packages/EasterEgg/src/com/android/egg/neko/Cat.java b/packages/EasterEgg/src/com/android/egg/neko/Cat.java
index cd59a73..bf09bc7 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/Cat.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/Cat.java
@@ -258,7 +258,7 @@
Notification.BubbleMetadata bubbs = new Notification.BubbleMetadata.Builder()
.setIntent(
- PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE))
+ PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE))
.setIcon(notificationIcon)
.setSuppressNotification(false)
.setDesiredHeight(context.getResources().getDisplayMetrics().heightPixels)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 1283f81..fd41f5f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -246,26 +246,28 @@
@Override
public void onActivityResult(int request, int result, Intent data) {
- if (request == REQUEST_TRUST_EXTERNAL_SOURCE && result == RESULT_OK) {
- // The user has just allowed this package to install other packages (via Settings).
- mAllowUnknownSources = true;
-
+ if (request == REQUEST_TRUST_EXTERNAL_SOURCE) {
// Log the fact that the app is requesting an install, and is now allowed to do it
// (before this point we could only log that it's requesting an install, but isn't
// allowed to do it yet).
String appOpStr =
AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
- mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid, mOriginatingPackage,
- mCallingAttributionTag,
+ int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid,
+ mOriginatingPackage, mCallingAttributionTag,
"Successfully started package installation activity");
-
- DialogFragment currentDialog =
- (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
- if (currentDialog != null) {
- currentDialog.dismissAllowingStateLoss();
+ if (appOpMode == AppOpsManager.MODE_ALLOWED) {
+ // The user has just allowed this package to install other packages
+ // (via Settings).
+ mAllowUnknownSources = true;
+ DialogFragment currentDialog =
+ (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
+ if (currentDialog != null) {
+ currentDialog.dismissAllowingStateLoss();
+ }
+ initiateInstall();
+ } else {
+ finish();
}
-
- initiateInstall();
} else {
finish();
}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index dd7058d..b898a4f 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -20,6 +20,7 @@
}
android {
+ namespace 'com.android.settingslib.spa.testutils'
compileSdk TARGET_SDK
buildToolsVersion = BUILD_TOOLS_VERSION
@@ -55,7 +56,7 @@
dependencies {
api project(":spa")
- api "androidx.arch.core:core-testing:2.1.0"
+ api "androidx.arch.core:core-testing:2.2.0-alpha01"
api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
api "com.google.truth:truth:1.1.3"
api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 0228142..b92b3d6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1316,7 +1316,7 @@
<!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
<string name="media_transfer_this_phone">This phone</string>
<!-- Sub status indicates device is not available due to an unknown error. [CHAR LIMIT=NONE] -->
- <string name="media_output_status_unknown_error">Unavailable due to unknown error</string>
+ <string name="media_output_status_unknown_error">Can\’t play on this device</string>
<!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
<string name="media_output_status_require_premium">Upgrade account to switch</string>
<!-- Sub status indicates device not support download content. [CHAR LIMIT=NONE] -->
@@ -1324,11 +1324,11 @@
<!-- Sub status indicates device need to wait after ad. [CHAR LIMIT=NONE] -->
<string name="media_output_status_try_after_ad">Try again after the ad</string>
<!-- Sub status indicates device is in low-power mode. [CHAR LIMIT=NONE] -->
- <string name="media_output_status_device_in_low_power_mode">Device in low power mode</string>
+ <string name="media_output_status_device_in_low_power_mode">Wake up device to play here</string>
<!-- Sub status indicates the device does not authorize the user. [CHAR LIMIT=NONE] -->
- <string name="media_output_status_unauthorized">Requires authorization</string>
+ <string name="media_output_status_unauthorized">Device not approved to play</string>
<!-- Sub status indicates the device does not support the current media track. [CHAR LIMIT=NONE] -->
- <string name="media_output_status_track_unsupported">Current media track not supported</string>
+ <string name="media_output_status_track_unsupported">Can\’t play this media here</string>
<!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
<string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index a82f070..96e875b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -70,6 +70,8 @@
"com.android.settings.category.ia.smart_battery_settings";
public static final String CATEGORY_COMMUNAL_SETTINGS =
"com.android.settings.category.ia.communal";
+ public static final String CATEGORY_MORE_SECURITY_PRIVACY_SETTINGS =
+ "com.android.settings.category.ia.more_security_privacy_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 85d4fab..1b832bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -106,6 +106,15 @@
mMediaDevices.clear();
mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
mRouterManager.registerScanRequest();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && !TextUtils.isEmpty(mPackageName)) {
+ RouteListingPreference routeListingPreference =
+ mRouterManager.getRouteListingPreference(mPackageName);
+ if (routeListingPreference != null) {
+ Api34Impl.onRouteListingPreferenceUpdated(null, routeListingPreference,
+ mPreferenceItemMap);
+ }
+ }
refreshDevices();
}
@@ -500,7 +509,8 @@
infos.add(transferableRoute);
}
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && !TextUtils.isEmpty(mPackageName)) {
RouteListingPreference routeListingPreference =
mRouterManager.getRouteListingPreference(mPackageName);
if (routeListingPreference != null) {
@@ -633,6 +643,7 @@
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
mPreferenceItemMap);
+ refreshDevices();
}
}
}
@@ -646,7 +657,7 @@
List<RouteListingPreference.Item> itemList = routeListingPreference.getItems();
for (RouteListingPreference.Item item : itemList) {
// Put suggested devices on the top first before further organization
- if (item.getFlags() == RouteListingPreference.Item.FLAG_SUGGESTED) {
+ if ((item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
finalizedItemList.add(0, item);
} else {
finalizedItemList.add(item);
@@ -695,6 +706,9 @@
@DoNotInline
static boolean preferRouteListingOrdering(MediaRouter2Manager mediaRouter2Manager,
String packageName) {
+ if (TextUtils.isEmpty(packageName)) {
+ return false;
+ }
RouteListingPreference routeListingPreference =
mediaRouter2Manager.getRouteListingPreference(packageName);
return routeListingPreference != null
@@ -705,6 +719,9 @@
@Nullable
static ComponentName getLinkedItemComponentName(
MediaRouter2Manager mediaRouter2Manager, String packageName) {
+ if (TextUtils.isEmpty(packageName)) {
+ return null;
+ }
RouteListingPreference routeListingPreference =
mediaRouter2Manager.getRouteListingPreference(packageName);
return routeListingPreference == null ? null
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 10305e5..4e18222 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -944,7 +944,7 @@
android:showWhenLocked="true"
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
- android:launchMode="singleInstance"
+ android:lockTaskMode="if_whitelisted"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index 7897934..442c6fa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -66,11 +66,28 @@
fun isPlaying(): Boolean = animator.isRunning
private fun applyConfigToShader() {
- rippleShader.setCenter(config.centerX, config.centerY)
- rippleShader.setMaxSize(config.maxWidth, config.maxHeight)
- rippleShader.rippleFill = config.shouldFillRipple
- rippleShader.pixelDensity = config.pixelDensity
- rippleShader.color = ColorUtils.setAlphaComponent(config.color, config.opacity)
- rippleShader.sparkleStrength = config.sparkleStrength
+ with(rippleShader) {
+ setCenter(config.centerX, config.centerY)
+ setMaxSize(config.maxWidth, config.maxHeight)
+ pixelDensity = config.pixelDensity
+ color = ColorUtils.setAlphaComponent(config.color, config.opacity)
+ sparkleStrength = config.sparkleStrength
+
+ assignFadeParams(baseRingFadeParams, config.baseRingFadeParams)
+ assignFadeParams(sparkleRingFadeParams, config.sparkleRingFadeParams)
+ assignFadeParams(centerFillFadeParams, config.centerFillFadeParams)
+ }
+ }
+
+ private fun assignFadeParams(
+ destFadeParams: RippleShader.FadeParams,
+ srcFadeParams: RippleShader.FadeParams?
+ ) {
+ srcFadeParams?.let {
+ destFadeParams.fadeInStart = it.fadeInStart
+ destFadeParams.fadeInEnd = it.fadeInEnd
+ destFadeParams.fadeOutStart = it.fadeOutStart
+ destFadeParams.fadeOutEnd = it.fadeOutEnd
+ }
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 773ac55..1786d13 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -20,8 +20,11 @@
val pixelDensity: Float = 1f,
var color: Int = Color.WHITE,
val opacity: Int = RIPPLE_DEFAULT_ALPHA,
- val shouldFillRipple: Boolean = false,
val sparkleStrength: Float = RIPPLE_SPARKLE_STRENGTH,
+ // Null means it uses default fade parameter values.
+ val baseRingFadeParams: RippleShader.FadeParams? = null,
+ val sparkleRingFadeParams: RippleShader.FadeParams? = null,
+ val centerFillFadeParams: RippleShader.FadeParams? = null,
val shouldDistort: Boolean = true
) {
companion object {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 74bc910..61ca90a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -82,7 +82,7 @@
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float radius = in_size.x * 0.5;
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
- float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
+ float inside = soften(sdCircle(p_distorted-in_center, radius * 1.25), in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
@@ -270,38 +270,6 @@
var currentHeight: Float = 0f
private set
- /**
- * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
- * False for a ring effect.
- *
- * <p>You must reset fade params after changing this.
- *
- * TODO(b/265326983): Remove this and only expose fade params.
- */
- var rippleFill: Boolean = false
- set(value) {
- if (value) {
- baseRingFadeParams.fadeOutStart = 1f
- baseRingFadeParams.fadeOutEnd = 1f
-
- centerFillFadeParams.fadeInStart = 0f
- centerFillFadeParams.fadeInEnd = 0f
- centerFillFadeParams.fadeOutStart = 1f
- centerFillFadeParams.fadeOutEnd = 1f
- } else {
- // Set back to the original fade parameters.
- // Ideally this should be set by the client as they know the initial value.
- baseRingFadeParams.fadeOutStart = DEFAULT_BASE_RING_FADE_OUT_START
- baseRingFadeParams.fadeOutEnd = DEFAULT_FADE_OUT_END
-
- centerFillFadeParams.fadeInStart = DEFAULT_FADE_IN_START
- centerFillFadeParams.fadeInEnd = DEFAULT_CENTER_FILL_FADE_IN_END
- centerFillFadeParams.fadeOutStart = DEFAULT_CENTER_FILL_FADE_OUT_START
- centerFillFadeParams.fadeOutEnd = DEFAULT_CENTER_FILL_FADE_OUT_END
- }
- field = value
- }
-
/** Parameters that are used to fade in/ out of the sparkle ring. */
val sparkleRingFadeParams =
FadeParams(
@@ -324,12 +292,7 @@
DEFAULT_FADE_OUT_END
)
- /**
- * Parameters that are used to fade in/ out of the center fill.
- *
- * <p>Note that if [rippleFill] is set to true, those will be ignored and the center fill will
- * be always full alpha.
- */
+ /** Parameters that are used to fade in/ out of the center fill. */
val centerFillFadeParams =
FadeParams(
DEFAULT_FADE_IN_START,
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 4483db8..1daff9f 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -27,22 +27,27 @@
android:layout_height="wrap_content"
/>
- <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
- android:id="@+id/icon_glow_ripple"
+ <FrameLayout
+ android:id="@+id/icon_container_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- />
+ >
+ <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+ android:id="@+id/icon_glow_ripple"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
- <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
- bounds while animating with the icon -->
- <com.android.internal.widget.CachingIconView
- android:id="@+id/app_icon"
- android:background="@drawable/media_ttt_chip_background_receiver"
- android:layout_width="@dimen/media_ttt_icon_size_receiver"
- android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center|bottom"
- android:alpha="0.0"
- android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
- />
+ <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
+ bounds while animating with the icon -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/app_icon"
+ android:background="@drawable/media_ttt_chip_background_receiver"
+ android:layout_width="@dimen/media_ttt_icon_size_receiver"
+ android:layout_height="@dimen/media_ttt_icon_size_receiver"
+ android:layout_gravity="center|bottom"
+ android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
+ />
+ </FrameLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 5d78e4e..2a27b47 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -45,8 +45,6 @@
<item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
<dimen name="controls_task_view_right_margin">8dp</dimen>
- <dimen name="status_bar_header_height_keyguard">42dp</dimen>
-
<dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
<dimen name="status_view_margin_horizontal">8dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 4f24d83..45b137a 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -16,8 +16,9 @@
*/
-->
<resources>
- <!-- Height of the status bar header bar when on Keyguard -->
- <dimen name="status_bar_header_height_keyguard">60dp</dimen>
+ <!-- Height of the status bar header bar when on Keyguard.
+ On large screens should be the same as the regular status bar. -->
+ <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
<!-- Size of user icon + frame in the qs user picker (incl. frame) -->
<dimen name="qs_framed_avatar_size">60dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 2b88e55..9ed9360 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -22,8 +22,6 @@
<dimen name="keyguard_split_shade_top_margin">72dp</dimen>
- <dimen name="status_bar_header_height_keyguard">56dp</dimen>
-
<dimen name="status_view_margin_horizontal">24dp</dimen>
<dimen name="qs_media_session_height_expanded">184dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 95675ce..209d5e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -38,13 +38,19 @@
public class Monitor {
private final String mTag = getClass().getSimpleName();
private final Executor mExecutor;
+ private final Set<Condition> mPreconditions;
private final HashMap<Condition, ArraySet<Subscription.Token>> mConditions = new HashMap<>();
private final HashMap<Subscription.Token, SubscriptionState> mSubscriptions = new HashMap<>();
private static class SubscriptionState {
private final Subscription mSubscription;
+
+ // A subscription must maintain a reference to any active nested subscription so that it may
+ // be later removed when the current subscription becomes invalid.
+ private Subscription.Token mNestedSubscriptionToken;
private Boolean mAllConditionsMet;
+ private boolean mActive;
SubscriptionState(Subscription subscription) {
mSubscription = subscription;
@@ -54,7 +60,27 @@
return mSubscription.mConditions;
}
- public void update() {
+ /**
+ * Signals that the {@link Subscription} is now being monitored and will receive updates
+ * based on its conditions.
+ */
+ private void setActive(boolean active) {
+ if (mActive == active) {
+ return;
+ }
+
+ mActive = active;
+
+ final Callback callback = mSubscription.getCallback();
+
+ if (callback == null) {
+ return;
+ }
+
+ callback.onActiveChanged(active);
+ }
+
+ public void update(Monitor monitor) {
final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
Evaluator.OP_AND);
// Consider unknown (null) as true
@@ -65,7 +91,50 @@
}
mAllConditionsMet = newAllConditionsMet;
- mSubscription.mCallback.onConditionsChanged(mAllConditionsMet);
+
+ final Subscription nestedSubscription = mSubscription.getNestedSubscription();
+
+ if (nestedSubscription != null) {
+ if (mAllConditionsMet && mNestedSubscriptionToken == null) {
+ // When all conditions are met for a subscription with a nested subscription
+ // that is not currently being monitored, add the nested subscription for
+ // monitor.
+ mNestedSubscriptionToken =
+ monitor.addSubscription(nestedSubscription, null);
+ } else if (!mAllConditionsMet && mNestedSubscriptionToken != null) {
+ // When conditions are not met and there is an active nested condition, remove
+ // the nested condition from monitoring.
+ removeNestedSubscription(monitor);
+ }
+ return;
+ }
+
+ mSubscription.getCallback().onConditionsChanged(mAllConditionsMet);
+ }
+
+ /**
+ * Invoked when the {@link Subscription} has been added to the {@link Monitor}.
+ */
+ public void onAdded() {
+ setActive(true);
+ }
+
+ /**
+ * Invoked when the {@link Subscription} has been removed from the {@link Monitor},
+ * allowing cleanup code to run.
+ */
+ public void onRemoved(Monitor monitor) {
+ setActive(false);
+ removeNestedSubscription(monitor);
+ }
+
+ private void removeNestedSubscription(Monitor monitor) {
+ if (mNestedSubscriptionToken == null) {
+ return;
+ }
+
+ monitor.removeSubscription(mNestedSubscriptionToken);
+ mNestedSubscriptionToken = null;
}
}
@@ -77,9 +146,20 @@
}
};
+ /**
+ * Constructor for injected use-cases. By default, no preconditions are present.
+ */
@Inject
public Monitor(@Main Executor executor) {
+ this(executor, Collections.emptySet());
+ }
+
+ /**
+ * Main constructor, allowing specifying preconditions.
+ */
+ public Monitor(Executor executor, Set<Condition> preconditions) {
mExecutor = executor;
+ mPreconditions = preconditions;
}
private void updateConditionMetState(Condition condition) {
@@ -91,7 +171,7 @@
return;
}
- subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
+ subscriptions.stream().forEach(token -> mSubscriptions.get(token).update(this));
}
/**
@@ -101,15 +181,25 @@
* @return A {@link Subscription.Token} that can be used to remove the subscription.
*/
public Subscription.Token addSubscription(@NonNull Subscription subscription) {
+ return addSubscription(subscription, mPreconditions);
+ }
+
+ private Subscription.Token addSubscription(@NonNull Subscription subscription,
+ Set<Condition> preconditions) {
+ // If preconditions are set on the monitor, set up as a nested condition.
+ final Subscription normalizedCondition = preconditions != null
+ ? new Subscription.Builder(subscription).addConditions(preconditions).build()
+ : subscription;
+
final Subscription.Token token = new Subscription.Token();
- final SubscriptionState state = new SubscriptionState(subscription);
+ final SubscriptionState state = new SubscriptionState(normalizedCondition);
mExecutor.execute(() -> {
if (shouldLog()) Log.d(mTag, "adding subscription");
mSubscriptions.put(token, state);
// Add and associate conditions.
- subscription.getConditions().stream().forEach(condition -> {
+ normalizedCondition.getConditions().stream().forEach(condition -> {
if (!mConditions.containsKey(condition)) {
mConditions.put(condition, new ArraySet<>());
condition.addCallback(mConditionCallback);
@@ -118,8 +208,10 @@
mConditions.get(condition).add(token);
});
+ state.onAdded();
+
// Update subscription state.
- state.update();
+ state.update(this);
});
return token;
@@ -139,7 +231,9 @@
return;
}
- mSubscriptions.remove(token).getConditions().forEach(condition -> {
+ final SubscriptionState removedSubscription = mSubscriptions.remove(token);
+
+ removedSubscription.getConditions().forEach(condition -> {
if (!mConditions.containsKey(condition)) {
Log.e(mTag, "condition not present:" + condition);
return;
@@ -153,6 +247,8 @@
mConditions.remove(condition);
}
});
+
+ removedSubscription.onRemoved(this);
});
}
@@ -168,12 +264,19 @@
private final Set<Condition> mConditions;
private final Callback mCallback;
- /**
- *
- */
- public Subscription(Set<Condition> conditions, Callback callback) {
+ // A nested {@link Subscription} is a special callback where the specified condition's
+ // active state is dependent on the conditions of the parent {@link Subscription} being met.
+ // Once active, the nested subscription's conditions are registered as normal with the
+ // monitor and its callback (which could also be a nested condition) is triggered based on
+ // those conditions. The nested condition will be removed from monitor if the outer
+ // subscription's conditions ever become invalid.
+ private final Subscription mNestedSubscription;
+
+ private Subscription(Set<Condition> conditions, Callback callback,
+ Subscription nestedSubscription) {
this.mConditions = Collections.unmodifiableSet(conditions);
this.mCallback = callback;
+ this.mNestedSubscription = nestedSubscription;
}
public Set<Condition> getConditions() {
@@ -184,6 +287,10 @@
return mCallback;
}
+ public Subscription getNestedSubscription() {
+ return mNestedSubscription;
+ }
+
/**
* A {@link Token} is an identifier that is associated with a {@link Subscription} which is
* registered with a {@link Monitor}.
@@ -196,14 +303,26 @@
*/
public static class Builder {
private final Callback mCallback;
+ private final Subscription mNestedSubscription;
private final ArraySet<Condition> mConditions;
+ private final ArraySet<Condition> mPreconditions;
/**
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
*/
public Builder(Callback callback) {
+ this(null, callback);
+ }
+
+ public Builder(Subscription nestedSubscription) {
+ this(nestedSubscription, null);
+ }
+
+ private Builder(Subscription nestedSubscription, Callback callback) {
+ mNestedSubscription = nestedSubscription;
mCallback = callback;
- mConditions = new ArraySet<>();
+ mConditions = new ArraySet();
+ mPreconditions = new ArraySet();
}
/**
@@ -217,11 +336,38 @@
}
/**
+ * Adds a set of {@link Condition} to be a precondition for {@link Subscription}.
+ *
+ * @return The updated {@link Builder}.
+ */
+ public Builder addPreconditions(Set<Condition> condition) {
+ if (condition == null) {
+ return this;
+ }
+ mPreconditions.addAll(condition);
+ return this;
+ }
+
+ /**
+ * Adds a {@link Condition} to be a precondition for {@link Subscription}.
+ *
+ * @return The updated {@link Builder}.
+ */
+ public Builder addPrecondition(Condition condition) {
+ mPreconditions.add(condition);
+ return this;
+ }
+
+ /**
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
*
* @return The updated {@link Builder}.
*/
public Builder addConditions(Set<Condition> condition) {
+ if (condition == null) {
+ return this;
+ }
+
mConditions.addAll(condition);
return this;
}
@@ -232,7 +378,11 @@
* @return The resulting {@link Subscription}.
*/
public Subscription build() {
- return new Subscription(mConditions, mCallback);
+ final Subscription subscription =
+ new Subscription(mConditions, mCallback, mNestedSubscription);
+ return !mPreconditions.isEmpty()
+ ? new Subscription(mPreconditions, null, subscription)
+ : subscription;
}
}
}
@@ -255,5 +405,13 @@
* only partial conditions have been fulfilled.
*/
void onConditionsChanged(boolean allConditionsMet);
+
+ /**
+ * Called when the active state of the {@link Subscription} changes.
+ * @param active {@code true} when changes to the conditions will affect the
+ * {@link Subscription}, {@code false} otherwise.
+ */
+ default void onActiveChanged(boolean active) {
+ }
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
index 9b73cc3..bd20777 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -25,7 +25,7 @@
import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
/** Extension of [TextView] which draws two shadows on the text (ambient and key shadows} */
-class DoubleShadowTextView
+open class DoubleShadowTextView
@JvmOverloads
constructor(
context: Context,
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index a70b4cd..1254e1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -24,8 +24,8 @@
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
-import android.widget.FrameLayout
import android.view.ViewTreeObserver
+import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -40,8 +40,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.log.dagger.KeyguardLargeClockLog
+import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockTickRate
@@ -53,22 +53,24 @@
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
*/
-open class ClockEventController @Inject constructor(
+open class ClockEventController
+@Inject
+constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -115,52 +117,59 @@
private var disposableHandle: DisposableHandle? = null
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
- private val mLayoutChangedListener = object : View.OnLayoutChangeListener {
- private var currentSmallClockView: View? = null
- private var currentLargeClockView: View? = null
- private var currentSmallClockLocation = IntArray(2)
- private var currentLargeClockLocation = IntArray(2)
+ private val mLayoutChangedListener =
+ object : View.OnLayoutChangeListener {
+ private var currentSmallClockView: View? = null
+ private var currentLargeClockView: View? = null
+ private var currentSmallClockLocation = IntArray(2)
+ private var currentLargeClockLocation = IntArray(2)
- override fun onLayoutChange(
- view: View?,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- val parent = (view?.parent) as FrameLayout
+ override fun onLayoutChange(
+ view: View?,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ val parent = (view?.parent) as FrameLayout
- // don't pass in negative bounds when clocks are in transition state
- if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
- return
- }
+ // don't pass in negative bounds when clocks are in transition state
+ if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
+ return
+ }
- // SMALL CLOCK
- if (parent.id == R.id.lockscreen_clock_view) {
- // view bounds have changed due to clock size changing (i.e. different character widths)
- // AND/OR the view has been translated when transitioning between small and large clock
- if (view != currentSmallClockView ||
- !view.locationOnScreen.contentEquals(currentSmallClockLocation)) {
- currentSmallClockView = view
- currentSmallClockLocation = view.locationOnScreen
- updateRegionSampler(view)
+ // SMALL CLOCK
+ if (parent.id == R.id.lockscreen_clock_view) {
+ // view bounds have changed due to clock size changing (i.e. different character
+ // widths)
+ // AND/OR the view has been translated when transitioning between small and
+ // large clock
+ if (
+ view != currentSmallClockView ||
+ !view.locationOnScreen.contentEquals(currentSmallClockLocation)
+ ) {
+ currentSmallClockView = view
+ currentSmallClockLocation = view.locationOnScreen
+ updateRegionSampler(view)
+ }
+ }
+ // LARGE CLOCK
+ else if (parent.id == R.id.lockscreen_clock_view_large) {
+ if (
+ view != currentLargeClockView ||
+ !view.locationOnScreen.contentEquals(currentLargeClockLocation)
+ ) {
+ currentLargeClockView = view
+ currentLargeClockLocation = view.locationOnScreen
+ updateRegionSampler(view)
+ }
+ }
}
}
- // LARGE CLOCK
- else if (parent.id == R.id.lockscreen_clock_view_large) {
- if (view != currentLargeClockView ||
- !view.locationOnScreen.contentEquals(currentLargeClockLocation)) {
- currentLargeClockView = view
- currentLargeClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
- }
- }
- }
private fun updateColors() {
val wallpaperManager = WallpaperManager.getInstance(context)
@@ -189,30 +198,33 @@
private fun updateRegionSampler(sampledRegion: View) {
regionSampler?.stopRegionSampler()
- regionSampler = createRegionSampler(
- sampledRegion,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- ::updateColors
- )?.apply { startRegionSampler() }
+ regionSampler =
+ createRegionSampler(
+ sampledRegion,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ ::updateColors
+ )
+ ?.apply { startRegionSampler() }
updateColors()
}
protected open fun createRegionSampler(
- sampledView: View?,
- mainExecutor: Executor?,
- bgExecutor: Executor?,
- regionSamplingEnabled: Boolean,
- updateColors: () -> Unit
+ sampledView: View?,
+ mainExecutor: Executor?,
+ bgExecutor: Executor?,
+ regionSamplingEnabled: Boolean,
+ updateColors: () -> Unit
): RegionSampler? {
return RegionSampler(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateColors)
+ updateColors
+ )
}
var regionSampler: RegionSampler? = null
@@ -224,64 +236,68 @@
private var smallClockIsDark = true
private var largeClockIsDark = true
- private val configListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- clock?.events?.onColorPaletteChanged(resources)
- updateColors()
- }
-
- override fun onDensityOrFontScaleChanged() {
- updateFontSizes()
- }
- }
-
- private val batteryCallback = object : BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- if (isKeyguardVisible && !isCharging && charging) {
- clock?.animations?.charge()
+ private val configListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ clock?.events?.onColorPaletteChanged(resources)
+ updateColors()
}
- isCharging = charging
- }
- }
- private val localeBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- clock?.events?.onLocaleChanged(Locale.getDefault())
+ override fun onDensityOrFontScaleChanged() {
+ updateFontSizes()
+ }
}
- }
- private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardVisibilityChanged(visible: Boolean) {
- isKeyguardVisible = visible
- if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
- if (!isKeyguardVisible) {
- clock?.animations?.doze(if (isDozing) 1f else 0f)
+ private val batteryCallback =
+ object : BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ if (isKeyguardVisible && !isCharging && charging) {
+ clock?.animations?.charge()
+ }
+ isCharging = charging
+ }
+ }
+
+ private val localeBroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ clock?.events?.onLocaleChanged(Locale.getDefault())
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ isKeyguardVisible = visible
+ if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (!isKeyguardVisible) {
+ clock?.animations?.doze(if (isDozing) 1f else 0f)
+ }
+ }
+
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
+ }
+
+ override fun onTimeFormatChanged(timeFormat: String) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ clock?.events?.onTimeZoneChanged(timeZone)
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onWeatherDataChanged(data: Weather?) {
+ if (data != null) {
+ clock?.events?.onWeatherDataChanged(data)
}
}
-
- smallTimeListener?.update(shouldTimeListenerRun)
- largeTimeListener?.update(shouldTimeListenerRun)
}
- override fun onTimeFormatChanged(timeFormat: String) {
- clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
-
- override fun onTimeZoneChanged(timeZone: TimeZone) {
- clock?.events?.onTimeZoneChanged(timeZone)
- }
-
- override fun onUserSwitchComplete(userId: Int) {
- clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
-
- override fun onWeatherDataChanged(data: Weather?) {
- if (data != null) {
- clock?.events?.onWeatherDataChanged(data)
- }
- }
- }
-
fun registerListeners(parent: View) {
if (isRegistered) {
return
@@ -295,17 +311,18 @@
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- disposableHandle = parent.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- listenForDozing(this)
- if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
- listenForDozeAmountTransition(this)
- listenForAnyStateToAodTransition(this)
- } else {
- listenForDozeAmount(this)
+ disposableHandle =
+ parent.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ listenForDozing(this)
+ if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ listenForDozeAmountTransition(this)
+ listenForAnyStateToAodTransition(this)
+ } else {
+ listenForDozeAmount(this)
+ }
}
}
- }
smallTimeListener?.update(shouldTimeListenerRun)
largeTimeListener?.update(shouldTimeListenerRun)
}
@@ -344,10 +361,18 @@
}
private fun updateFontSizes() {
- clock?.smallClock?.events?.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
- clock?.largeClock?.events?.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+ clock
+ ?.smallClock
+ ?.events
+ ?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ clock
+ ?.largeClock
+ ?.events
+ ?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
}
private fun handleDoze(doze: Float) {
@@ -359,68 +384,59 @@
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
- return scope.launch {
- keyguardInteractor.dozeAmount.collect {
- handleDoze(it)
- }
- }
+ return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } }
}
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.dozeAmountTransition.collect {
- handleDoze(it.value)
- }
+ keyguardTransitionInteractor.dozeAmountTransition.collect { handleDoze(it.value) }
}
}
/**
- * When keyguard is displayed again after being gone, the clock must be reset to full
- * dozing.
+ * When keyguard is displayed again after being gone, the clock must be reset to full dozing.
*/
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToAodTransition.filter {
- it.transitionState == TransitionState.FINISHED
- }.collect {
- handleDoze(1f)
- }
+ keyguardTransitionInteractor.anyStateToAodTransition
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .collect { handleDoze(1f) }
}
}
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
- combine (
- keyguardInteractor.dozeAmount,
- keyguardInteractor.isDozing,
- ) { localDozeAmount, localIsDozing ->
- localDozeAmount > dozeAmount || localIsDozing
- }
- .collect { localIsDozing ->
- isDozing = localIsDozing
- }
+ combine(
+ keyguardInteractor.dozeAmount,
+ keyguardInteractor.isDozing,
+ ) { localDozeAmount, localIsDozing ->
+ localDozeAmount > dozeAmount || localIsDozing
+ }
+ .collect { localIsDozing -> isDozing = localIsDozing }
}
}
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
- val predrawListener = ViewTreeObserver.OnPreDrawListener {
- clockFace.events.onTimeTick()
- true
- }
-
- val secondsRunnable = object : Runnable {
- override fun run() {
- if (!isRunning) {
- return
- }
-
- executor.executeDelayed(this, 990)
+ val predrawListener =
+ ViewTreeObserver.OnPreDrawListener {
clockFace.events.onTimeTick()
+ true
}
- }
+
+ val secondsRunnable =
+ object : Runnable {
+ override fun run() {
+ if (!isRunning) {
+ return
+ }
+
+ executor.executeDelayed(this, 990)
+ clockFace.events.onTimeTick()
+ }
+ }
var isRunning: Boolean = false
private set
@@ -432,7 +448,9 @@
isRunning = true
when (clockFace.events.tickRate) {
- ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
+ ClockTickRate.PER_MINUTE -> {
+ /* Handled by KeyguardClockSwitchController */
+ }
ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
ClockTickRate.PER_FRAME -> {
clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
@@ -442,7 +460,9 @@
}
fun stop() {
- if (!isRunning) { return }
+ if (!isRunning) {
+ return
+ }
isRunning = false
clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 2c7eceb..379c78a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,9 +16,11 @@
package com.android.keyguard.logging
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
import com.android.systemui.log.dagger.KeyguardLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -76,4 +78,46 @@
{ "$str1 msgId: $str2 msg: $str3" }
)
}
+
+ fun logUpdateDeviceEntryIndication(
+ animate: Boolean,
+ visible: Boolean,
+ dozing: Boolean,
+ ) {
+ buffer.log(
+ KeyguardIndicationController.TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = animate
+ bool2 = visible
+ bool3 = dozing
+ },
+ { "updateDeviceEntryIndication animate:$bool1 visible:$bool2 dozing $bool3" }
+ )
+ }
+
+ fun logKeyguardSwitchIndication(
+ type: Int,
+ message: String?,
+ ) {
+ buffer.log(
+ KeyguardIndicationController.TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = type
+ str1 = message
+ },
+ { "keyguardSwitchIndication ${getKeyguardSwitchIndicationNonSensitiveLog(int1, str1)}" }
+ )
+ }
+
+ fun getKeyguardSwitchIndicationNonSensitiveLog(type: Int, message: String?): String {
+ // only show the battery string. other strings may contain sensitive info
+ return if (type == KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY) {
+ "type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}" +
+ " message=$message"
+ } else {
+ "type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}"
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 9b2e6b8..d3fe2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -24,6 +24,7 @@
import androidx.annotation.IntDef;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -64,6 +65,7 @@
2000L + KeyguardIndicationTextView.Y_IN_DURATION;
private final StatusBarStateController mStatusBarStateController;
+ private final KeyguardLogger mLogger;
private final float mMaxAlpha;
private final ColorStateList mInitialTextColorState;
@@ -85,7 +87,8 @@
public KeyguardIndicationRotateTextViewController(
KeyguardIndicationTextView view,
@Main DelayableExecutor executor,
- StatusBarStateController statusBarStateController
+ StatusBarStateController statusBarStateController,
+ KeyguardLogger logger
) {
super(view);
mMaxAlpha = view.getAlpha();
@@ -93,6 +96,7 @@
mInitialTextColorState = mView != null
? mView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
mStatusBarStateController = statusBarStateController;
+ mLogger = logger;
init();
}
@@ -259,6 +263,8 @@
mLastIndicationSwitch = SystemClock.uptimeMillis();
if (!TextUtils.equals(previousMessage, mCurrMessage)
|| previousIndicationType != mCurrIndicationType) {
+ mLogger.logKeyguardSwitchIndication(type,
+ mCurrMessage != null ? mCurrMessage.toString() : null);
mView.switchIndication(mIndicationMessages.get(type));
}
@@ -352,9 +358,10 @@
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardIndicationRotatingTextViewController:");
- pw.println(" currentMessage=" + mView.getText());
+ pw.println(" currentTextViewMessage=" + mView.getText());
+ pw.println(" currentStoredMessage=" + mView.getMessage());
pw.println(" dozing:" + mIsDozing);
- pw.println(" queue:" + mIndicationQueue.toString());
+ pw.println(" queue:" + mIndicationQueue);
pw.println(" showNextIndicationRunnable:" + mShowNextIndicationRunnable);
if (hasIndications()) {
@@ -398,4 +405,40 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndicationType{}
+
+ /**
+ * Get human-readable string representation of the indication type.
+ */
+ public static String indicationTypeToString(@IndicationType int type) {
+ switch (type) {
+ case INDICATION_TYPE_NONE:
+ return "none";
+ case INDICATION_TYPE_DISCLOSURE:
+ return "disclosure";
+ case INDICATION_TYPE_OWNER_INFO:
+ return "owner_info";
+ case INDICATION_TYPE_LOGOUT:
+ return "logout";
+ case INDICATION_TYPE_BATTERY:
+ return "battery";
+ case INDICATION_TYPE_ALIGNMENT:
+ return "alignment";
+ case INDICATION_TYPE_TRANSIENT:
+ return "transient";
+ case INDICATION_TYPE_TRUST:
+ return "trust";
+ case INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE:
+ return "persistent_unlock_message";
+ case INDICATION_TYPE_USER_LOCKED:
+ return "user_locked";
+ case INDICATION_TYPE_REVERSE_CHARGING:
+ return "reverse_charging";
+ case INDICATION_TYPE_BIOMETRIC_MESSAGE:
+ return "biometric_message";
+ case INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP:
+ return "biometric_message_followup";
+ default:
+ return "unknown[" + type + "]";
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 81a5828..8715d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -34,6 +34,7 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -56,9 +57,14 @@
private fun listenForDreamingToLockscreen() {
scope.launch {
- // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
- // otherwise would have gone through OCCLUDED first
- keyguardInteractor.isAbleToDream
+ // Dependending on the dream, either dream state or occluded change will change first,
+ // so listen for both
+ combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) {
+ isAbleToDream,
+ isKeyguardOccluded ->
+ isAbleToDream && isKeyguardOccluded
+ }
+ .distinctUntilChanged()
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 3d39da6..7e86a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
@@ -31,7 +33,6 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.CommandQueue.Callbacks
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -41,7 +42,9 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -52,6 +55,7 @@
constructor(
private val repository: KeyguardRepository,
private val commandQueue: CommandQueue,
+ featureFlags: FeatureFlags,
) {
/**
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -129,6 +133,29 @@
*/
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+ /** Keyguard is present and is not occluded. */
+ val isKeyguardVisible: Flow<Boolean> =
+ combine(isKeyguardShowing, isKeyguardOccluded) { showing, occluded -> showing && !occluded }
+
+ /** Whether camera is launched over keyguard. */
+ var isSecureCameraActive =
+ if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ combine(
+ isKeyguardVisible,
+ repository.isBouncerShowing,
+ onCameraLaunchDetected,
+ ) { isKeyguardVisible, isBouncerShowing, cameraLaunchEvent ->
+ when {
+ isKeyguardVisible -> false
+ isBouncerShowing -> false
+ else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
+ }
+ }
+ .onStart { emit(false) }
+ } else {
+ flowOf(false)
+ }
+
/** The approximate location on the screen of the fingerprint sensor, if one is available. */
val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 348d941..ccd4060 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -79,10 +79,10 @@
}
}
-/**
- * Each time the boolean flow is updated with a new value that's different from the previous value,
- * logs the new value to the given [tableLogBuffer].
- */
+// Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
+// above [logDiffsForTable] method.
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<Boolean>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
@@ -100,10 +100,8 @@
newVal
}
}
-/**
- * Each time the Int flow is updated with a new value that's different from the previous value, logs
- * the new value to the given [tableLogBuffer].
- */
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<Int>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
@@ -122,10 +120,26 @@
}
}
-/**
- * Each time the String? flow is updated with a new value that's different from the previous value,
- * logs the new value to the given [tableLogBuffer].
- */
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+fun Flow<Int?>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: Int?,
+): Flow<Int?> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+ if (prevVal != newVal) {
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+ }
+ newVal
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<String?>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
@@ -143,3 +157,23 @@
newVal
}
}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+fun <T> Flow<List<T>>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: List<T>,
+): Flow<List<T>> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(columnPrefix, columnName, initialValue.toString())
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+ if (prevVal != newVal) {
+ // TODO(b/267761156): Can we log list changes without using toString?
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
+ }
+ newVal
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 68c297f..4880f80 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -27,7 +27,7 @@
var columnName: String = "",
var type: DataType = DataType.EMPTY,
var bool: Boolean = false,
- var int: Int = 0,
+ var int: Int? = null,
var str: String? = null,
) {
/** Resets to default values so that the object can be recycled. */
@@ -54,7 +54,7 @@
}
/** Sets this to store an int change. */
- fun set(value: Int) {
+ fun set(value: Int?) {
type = DataType.INT
int = value
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 2c299d6..1712dab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -138,7 +138,7 @@
}
/** Logs a Int change. */
- fun logChange(prefix: String, columnName: String, value: Int) {
+ fun logChange(prefix: String, columnName: String, value: Int?) {
logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
}
@@ -155,7 +155,7 @@
change.set(value)
}
- private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int?) {
val change = obtain(timestamp, prefix, columnName)
change.set(value)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 1d000eb..6076e58 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -1139,8 +1139,10 @@
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* opacity= */ 100,
- /* shouldFillRipple= */ false,
/* sparkleStrength= */ 0f,
+ /* baseRingFadeParams= */ null,
+ /* sparkleRingFadeParams= */ null,
+ /* centerFillFadeParams= */ null,
/* shouldDistort= */ false
)
);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d555d05..99e5740 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -179,6 +179,7 @@
if (mCurrentActivePosition == position) {
mCurrentActivePosition = -1;
}
+ mStatusIcon.setVisibility(View.GONE);
if (mController.isAnyDeviceTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
@@ -233,7 +234,7 @@
setUpDeviceIcon(device);
mSubTitleText.setText(device.getSubtextString());
Drawable deviceStatusIcon =
- isActiveWithOngoingSession ? mContext.getDrawable(
+ device.hasOngoingSession() ? mContext.getDrawable(
R.drawable.media_output_status_session)
: Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
device,
@@ -331,7 +332,19 @@
setSingleLineLayout(getItemTitle(device));
if (mController.isAdvancedLayoutSupported()
&& mController.isSubStatusSupported()) {
- updateClickActionBasedOnSelectionBehavior(device);
+ Drawable deviceStatusIcon =
+ device.hasOngoingSession() ? mContext.getDrawable(
+ R.drawable.media_output_status_session)
+ : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
+ device,
+ mContext);
+ if (deviceStatusIcon != null) {
+ updateDeviceStatusIcon(deviceStatusIcon);
+ mStatusIcon.setVisibility(View.VISIBLE);
+ }
+ updateTwoLineLayoutContentAlpha(
+ updateClickActionBasedOnSelectionBehavior(device)
+ ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
} else {
updateFullItemClickListener(v -> onItemClick(v, device));
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index fbd0079..a174b45 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.Bundle;
+import android.util.FeatureFlagUtils;
import android.view.View;
import android.view.WindowManager;
@@ -99,10 +100,18 @@
@Override
public boolean isBroadcastSupported() {
boolean isBluetoothLeDevice = false;
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
+ if (FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) {
+ if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
mMediaOutputController.getCurrentConnectedMediaDevice());
+ }
+ } else {
+ // To decouple LE Audio Broadcast and Unicast, it always displays the button when there
+ // is no LE Audio device connected to the phone
+ isBluetoothLeDevice = true;
}
+
return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 8000cd8..fab8c06 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -16,7 +16,9 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.animation.TimeInterpolator
import android.annotation.SuppressLint
+import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
import android.graphics.Rect
@@ -31,8 +33,10 @@
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
@@ -101,6 +105,13 @@
fitInsetsTypes = 0 // Ignore insets from all system bars
}
+ // Value animator that controls the bouncing animation of views.
+ private val bounceAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+ repeatCount = ValueAnimator.INFINITE
+ repeatMode = ValueAnimator.REVERSE
+ duration = ICON_BOUNCE_ANIM_DURATION
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -203,44 +214,52 @@
val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
- iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
+
+ val iconContainerView = currentView.getIconContainerView()
+ iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
}
override fun animateViewIn(view: ViewGroup) {
- val appIconView = view.getAppIconView()
+ val iconContainerView = view.getIconContainerView()
val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
- animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f)
- animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f)
+ val translationYBy = getTranslationAmount()
+ // Make the icon container view starts animation from bottom of the screen.
+ iconContainerView.translationY += rippleController.getReceiverIconSize()
+ animateViewTranslationAndFade(
+ iconContainerView,
+ translationYBy = -1 * translationYBy,
+ alphaEndValue = 1f,
+ Interpolators.EMPHASIZED_DECELERATE,
+ ) {
+ animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO)
+ }
rippleController.expandToInProgressState(rippleView, iconRippleView)
}
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
- val appIconView = view.getAppIconView()
- val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
+ val iconContainerView = view.getIconContainerView()
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+ val translationYBy = getTranslationAmount()
+
+ // Remove update listeners from bounce animator to prevent any conflict with
+ // translation animation.
+ bounceAnimator.removeAllUpdateListeners()
+ bounceAnimator.cancel()
if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
rippleController.expandToSuccessState(rippleView, onAnimationEnd)
animateViewTranslationAndFade(
- iconRippleView,
- -1 * getTranslationAmount(),
- 0f,
- translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
- alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
- )
- animateViewTranslationAndFade(
- appIconView,
- -1 * getTranslationAmount(),
+ iconContainerView,
+ -1 * translationYBy,
0f,
translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
)
} else {
rippleController.collapseRipple(rippleView, onAnimationEnd)
- animateViewTranslationAndFade(iconRippleView, getTranslationAmount(), 0f)
- animateViewTranslationAndFade(appIconView, getTranslationAmount(), 0f)
+ animateViewTranslationAndFade(iconContainerView, translationYBy, 0f)
}
}
@@ -252,15 +271,19 @@
/** Animation of view translation and fading. */
private fun animateViewTranslationAndFade(
- view: View,
+ view: ViewGroup,
translationYBy: Float,
alphaEndValue: Float,
+ interpolator: TimeInterpolator? = null,
translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION,
alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
+ onAnimationEnd: Runnable? = null,
) {
view.animate()
.translationYBy(translationYBy)
+ .setInterpolator(interpolator)
.setDuration(translationDuration)
+ .withEndAction { onAnimationEnd?.run() }
.start()
view.animate()
.alpha(alphaEndValue)
@@ -270,17 +293,42 @@
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Float {
- return rippleController.getRippleSize() * 0.5f -
- rippleController.getReceiverIconSize()
+ return rippleController.getRippleSize() * 0.5f
}
private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon)
}
+ private fun View.getIconContainerView(): ViewGroup {
+ return this.requireViewById(R.id.icon_container_view)
+ }
+
+ private fun animateBouncingView(iconContainerView: ViewGroup, translationYBy: Float) {
+ if (bounceAnimator.isStarted) {
+ return
+ }
+
+ addViewToBounceAnimation(iconContainerView, translationYBy)
+
+ // In order not to announce description every time the view animate.
+ iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
+ bounceAnimator.start()
+ }
+
+ private fun addViewToBounceAnimation(view: View, translationYBy: Float) {
+ val prevTranslationY = view.translationY
+ bounceAnimator.addUpdateListener { updateListener ->
+ val progress = updateListener.animatedValue as Float
+ view.translationY = prevTranslationY + translationYBy * progress
+ }
+ }
+
companion object {
private const val ICON_TRANSLATION_ANIM_DURATION = 500L
+ private const val ICON_BOUNCE_ANIM_DURATION = 750L
private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L
+ private const val BOUNCE_TRANSLATION_RATIO = 0.15f
private val ICON_ALPHA_ANIM_DURATION = 5.frames
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 997370b..4ff082a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -119,13 +119,19 @@
private fun removeRippleFill() {
with(rippleShader) {
+ // Set back to default because we modified them in [setupRippleFadeParams].
baseRingFadeParams.fadeOutStart = RippleShader.DEFAULT_BASE_RING_FADE_OUT_START
baseRingFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
centerFillFadeParams.fadeInStart = RippleShader.DEFAULT_FADE_IN_START
centerFillFadeParams.fadeInEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_IN_END
- centerFillFadeParams.fadeOutStart = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_START
- centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_END
+
+ // To avoid a seam showing up, we should match either:
+ // 1. baseRingFadeParams#fadeInEnd and centerFillFadeParams#fadeOutStart
+ // 2. baseRingFadeParams#fadeOutStart and centerFillFadeOutStart
+ // Here we go with 1 to fade in the centerFill faster.
+ centerFillFadeParams.fadeOutStart = baseRingFadeParams.fadeInEnd
+ centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1678c6e..3088d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -46,10 +46,10 @@
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
import javax.inject.Qualifier
import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
@@ -110,6 +110,12 @@
@Provides
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
+ fun provideCallerPackageName(activity: MediaProjectionAppSelectorActivity): String? =
+ activity.callingPackage
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
fun bindConfigurationController(
activity: MediaProjectionAppSelectorActivity
): ConfigurationController = ConfigurationControllerImpl(activity)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 52c7ca3..219629b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -36,16 +36,16 @@
private val flags: FeatureFlags,
@HostUserHandle private val hostUserHandle: UserHandle,
@MediaProjectionAppSelector private val scope: CoroutineScope,
- @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
+ @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
+ @MediaProjectionAppSelector private val callerPackageName: String?
) {
fun init() {
scope.launch {
val recentTasks = recentTaskListProvider.loadRecentTasks()
- val tasks = recentTasks
- .filterDevicePolicyRestrictedTasks()
- .sortedTasks()
+ val tasks =
+ recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks()
view.bind(tasks)
}
@@ -67,8 +67,13 @@
filter { UserHandle.of(it.userId) == hostUserHandle }
}
+ private fun List<RecentTask>.filterAppSelector(): List<RecentTask> = filter {
+ // Only take tasks that is not the app selector
+ it.topActivityComponent != appSelectorComponentName
+ }
+
private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
// Show normal tasks first and only then tasks with opened app selector
- it.topActivityComponent == appSelectorComponentName
+ it.topActivityComponent?.packageName == callerPackageName
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
new file mode 100644
index 0000000..7db293d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -0,0 +1,27 @@
+/*
+ * 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.process;
+
+/**
+ * A simple wrapper that provides access to process-related details. This facilitates testing by
+ * providing a mockable target around these details.
+ */
+public class ProcessWrapper {
+ public int getUserHandleIdentifier() {
+ return android.os.Process.myUserHandle().getIdentifier();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java
new file mode 100644
index 0000000..5a21ea0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java
@@ -0,0 +1,48 @@
+/*
+ * 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.process.condition;
+
+import com.android.systemui.process.ProcessWrapper;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.condition.Condition;
+
+import javax.inject.Inject;
+
+/**
+ * {@link UserProcessCondition} provides a signal when the process handle belongs to the current
+ * user.
+ */
+public class UserProcessCondition extends Condition {
+ private final ProcessWrapper mProcessWrapper;
+ private final UserTracker mUserTracker;
+
+ @Inject
+ public UserProcessCondition(ProcessWrapper processWrapper, UserTracker userTracker) {
+ mProcessWrapper = processWrapper;
+ mUserTracker = userTracker;
+ }
+
+ @Override
+ protected void start() {
+ updateCondition(mUserTracker.getUserId()
+ == mProcessWrapper.getUserHandleIdentifier());
+ }
+
+ @Override
+ protected void stop() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8d68bce..dff79c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -96,6 +96,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -128,7 +129,7 @@
@SysUISingleton
public class KeyguardIndicationController {
- private static final String TAG = "KeyguardIndication";
+ public static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
@@ -328,9 +329,11 @@
mInitialTextColorState = mTopIndicationView != null
? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
mRotateTextViewController = new KeyguardIndicationRotateTextViewController(
- mLockScreenIndicationView,
- mExecutor,
- mStatusBarStateController);
+ mLockScreenIndicationView,
+ mExecutor,
+ mStatusBarStateController,
+ mKeyguardLogger
+ );
updateDeviceEntryIndication(false /* animate */);
updateOrganizedOwnedDevice();
if (mBroadcastReceiver == null) {
@@ -842,6 +845,7 @@
* may continuously be cycled through.
*/
protected final void updateDeviceEntryIndication(boolean animate) {
+ mKeyguardLogger.logUpdateDeviceEntryIndication(animate, mVisible, mDozing);
if (!mVisible) {
return;
}
@@ -1429,6 +1433,7 @@
public void onKeyguardShowingChanged() {
// All transient messages are gone the next time keyguard is shown
if (!mKeyguardStateController.isShowing()) {
+ mKeyguardLogger.log(TAG, LogLevel.DEBUG, "clear messages");
mTopIndicationView.clearMessages();
mRotateTextViewController.clearMessages();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index d24469e..b1553b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -165,6 +165,13 @@
}
}
+ /**
+ * Get the message that should be shown after the previous text animates out.
+ */
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
private AnimatorSet getOutAnimator() {
AnimatorSet animatorSet = new AnimatorSet();
Animator fadeOut = ObjectAnimator.ofFloat(this, View.ALPHA, 0f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 416bc71..5408afb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -163,7 +163,8 @@
for (int i = currentSlots.size() - 1; i >= 0; i--) {
Slot s = currentSlots.get(i);
slotsToReAdd.put(s, s.getHolderList());
- removeAllIconsForSlot(s.getName());
+ // Don't force here because the new pipeline properly handles the tuner settings
+ removeAllIconsForSlot(s.getName(), /* force */ false);
}
// Add them all back
@@ -285,7 +286,7 @@
// Because of the way we cache the icon holders, we need to remove everything any time
// we get a new set of subscriptions. This might change in the future, but is required
// to support demo mode for now
- removeAllIconsForSlot(slotName);
+ removeAllIconsForSlot(slotName, /* force */ true);
Collections.reverse(subIds);
@@ -428,6 +429,14 @@
/** */
@Override
public void removeIcon(String slot, int tag) {
+ // If the new pipeline is on for this icon, don't allow removal, since the new pipeline
+ // will never call this method
+ if (mStatusBarPipelineFlags.isIconControlledByFlags(slot)) {
+ Log.i(TAG, "Ignoring removal of (" + slot + "). "
+ + "It should be controlled elsewhere");
+ return;
+ }
+
if (mStatusBarIconList.getIconHolder(slot, tag) == null) {
return;
}
@@ -444,6 +453,18 @@
/** */
@Override
public void removeAllIconsForSlot(String slotName) {
+ removeAllIconsForSlot(slotName, /* force */ false);
+ }
+
+ private void removeAllIconsForSlot(String slotName, Boolean force) {
+ // If the new pipeline is on for this icon, don't allow removal, since the new pipeline
+ // will never call this method
+ if (!force && mStatusBarPipelineFlags.isIconControlledByFlags(slotName)) {
+ Log.i(TAG, "Ignoring removal of (" + slotName + "). "
+ + "It should be controlled elsewhere");
+ return;
+ }
+
Slot slot = mStatusBarIconList.getSlot(slotName);
if (!slot.hasIconsInSlot()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 15fed32..4a684d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline
+import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -23,7 +24,15 @@
/** All flagging methods related to the new status bar pipeline (see b/238425913). */
@SysUISingleton
-class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+class StatusBarPipelineFlags
+@Inject
+constructor(
+ context: Context,
+ private val featureFlags: FeatureFlags,
+) {
+ private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile)
+ private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi)
+
/** True if we should display the mobile icons using the new status bar data pipeline. */
fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
@@ -54,4 +63,13 @@
*/
fun useDebugColoring(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
+
+ /**
+ * For convenience in the StatusBarIconController, we want to gate some actions based on slot
+ * name and the flag together.
+ *
+ * @return true if this icon is controlled by any of the status bar pipeline flags
+ */
+ fun isIconControlledByFlags(slotName: String): Boolean =
+ slotName == wifiSlot && useNewWifiIcon() || slotName == mobileSlot && useNewMobileIcons()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt
new file mode 100644
index 0000000..2ac9ab3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for mobile data that's **the same across all connections**.
+ *
+ * This buffer should only be used for the mobile parent classes like [MobileConnectionsRepository]
+ * and [MobileIconsInteractor]. It should *not* be used for classes that represent an individual
+ * connection, like [MobileConnectionRepository] or [MobileIconInteractor].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MobileSummaryLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 5f3b0dc..60de1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -118,5 +118,12 @@
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30)
}
+
+ @Provides
+ @SysUISingleton
+ @MobileSummaryLog
+ fun provideMobileSummaryLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("MobileSummaryLog", 100)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
index e618905..97a537a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.net.NetworkCapabilities
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/** Provides information about a mobile network connection */
data class MobileConnectivityModel(
@@ -24,4 +26,24 @@
val isConnected: Boolean = false,
/** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
val isValidated: Boolean = false,
-)
+) : Diffable<MobileConnectivityModel> {
+ // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+ override fun logDiffs(prevVal: MobileConnectivityModel, row: TableRowLogger) {
+ if (prevVal.isConnected != isConnected) {
+ row.logChange(COL_IS_CONNECTED, isConnected)
+ }
+ if (prevVal.isValidated != isValidated) {
+ row.logChange(COL_IS_VALIDATED, isValidated)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_IS_CONNECTED, isConnected)
+ row.logChange(COL_IS_VALIDATED, isValidated)
+ }
+
+ companion object {
+ private const val COL_IS_CONNECTED = "isConnected"
+ private const val COL_IS_VALIDATED = "isValidated"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index c783b12..f5041d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -91,9 +91,6 @@
.map { it.toMobileConnectionModel() }
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
- // TODO(b/238425913): Add logging to this class.
- // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.
-
// Carrier merged is never roaming.
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index dd2cc92..f17791b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -117,11 +117,22 @@
override val connectionInfo =
activeRepo
.flatMapLatest { it.connectionInfo }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ initialValue = activeRepo.value.connectionInfo.value,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
override val dataEnabled =
activeRepo
.flatMapLatest { it.dataEnabled }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = "dataEnabled",
+ initialValue = activeRepo.value.dataEnabled.value,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
override val numberOfLevels =
@@ -132,6 +143,11 @@
override val networkName =
activeRepo
.flatMapLatest { it.networkName }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ initialValue = activeRepo.value.networkName.value,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
class Factory
@@ -168,7 +184,7 @@
const val MOBILE_CONNECTION_BUFFER_SIZE = 100
/** Returns a log buffer name for a mobile connection with the given [subId]. */
- fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+ fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 76fef35..cfc4cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -36,7 +36,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -62,7 +61,6 @@
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
@@ -87,7 +85,7 @@
private val mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
- mobileLogger: TableLogBuffer,
+ override val tableLogBuffer: TableLogBuffer,
scope: CoroutineScope,
) : MobileConnectionRepository {
init {
@@ -101,8 +99,6 @@
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
- override val tableLogBuffer: TableLogBuffer = mobileLogger
-
/**
* This flow defines the single shared connection to system_server via TelephonyCallback. Any
* new callback should be added to this listener and funneled through callbackEvents via a data
@@ -243,11 +239,6 @@
val initial = MobileConnectionModel()
callbackEvents
.scan(initial, ::updateConnectionState)
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "MobileConnection ($subId)",
- initialValue = initial,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
@@ -285,24 +276,12 @@
intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
}
}
- .distinctUntilChanged()
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "",
- initialValue = defaultNetworkName,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
override val dataEnabled = run {
val initial = telephonyManager.isDataConnectionAllowed
callbackEvents
.mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "",
- columnName = "dataEnabled",
- initialValue = initial
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 10f48a3..c660d31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -42,6 +42,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -82,6 +85,7 @@
private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
mobileMappingsProxy: MobileMappingsProxy,
broadcastDispatcher: BroadcastDispatcher,
private val context: Context,
@@ -114,6 +118,12 @@
}
}
.distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "carrierMergedSubId",
+ initialValue = null,
+ )
.stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
@@ -139,8 +149,14 @@
override val subscriptions: StateFlow<List<SubscriptionModel>> =
merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
- .logInputChange(logger, "onSubscriptionsChanged")
.onEach { infos -> updateRepos(infos) }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "subscriptions",
+ initialValue = listOf(),
+ )
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
/** StateFlow that keeps track of the current active mobile data subscription */
@@ -157,7 +173,12 @@
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
.distinctUntilChanged()
- .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "activeSubId",
+ initialValue = INVALID_SUBSCRIPTION_ID,
+ )
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
@@ -171,7 +192,12 @@
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
}
.distinctUntilChanged()
- .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "defaultSubId",
+ initialValue = SubscriptionManager.getDefaultDataSubscriptionId(),
+ )
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
@@ -247,7 +273,11 @@
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
- .logInputChange(logger, "defaultMobileNetworkConnectivity")
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+ initialValue = MobileConnectivityModel(),
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
/**
@@ -321,4 +351,8 @@
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
)
+
+ companion object {
+ private const val LOGGING_PREFIX = "Repo"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 9cdff96..9b7614c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -33,7 +33,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
@@ -109,6 +111,9 @@
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
val numberOfLevels: StateFlow<Int>
+
+ /** See [MobileIconsInteractor.isForceHidden]. */
+ val isForceHidden: Flow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
@@ -124,6 +129,7 @@
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
defaultDataSubId: StateFlow<Int>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,
+ override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo
@@ -181,6 +187,16 @@
else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
+ .distinctUntilChanged()
+ .onEach {
+ // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the
+ // [Diffable] interface.
+ tableLogBuffer.logChange(
+ prefix = "",
+ columnName = "networkTypeIcon",
+ value = it.name
+ )
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val isEmergencyOnly: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 0e4a432..94f7af4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -23,12 +23,17 @@
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -42,8 +47,8 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -88,6 +93,10 @@
val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
val isUserSetup: StateFlow<Boolean>
+
+ /** True if we're configured to force-hide the mobile icons and false otherwise. */
+ val isForceHidden: Flow<Boolean>
+
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid
@@ -104,6 +113,8 @@
private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
private val logger: ConnectivityPipelineLogger,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
+ connectivityRepository: ConnectivityRepository,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
@@ -173,7 +184,13 @@
}
}
.distinctUntilChanged()
- .onEach { logger.logFilteredSubscriptionsChanged(it) }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "filteredSubscriptions",
+ initialValue = listOf(),
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
@@ -195,6 +212,12 @@
delay(2000)
emit(false)
}
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "forcingValidation",
+ initialValue = false,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -211,6 +234,12 @@
networkConnectivity
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+ initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
+ )
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -259,10 +288,21 @@
!connectivityModel.isValidated
}
}
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "isDefaultConnectionFailed",
+ initialValue = false,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
+ override val isForceHidden: Flow<Boolean> =
+ connectivityRepository.forceHiddenSlots
+ .map { it.contains(ConnectivitySlot.MOBILE) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
@@ -275,6 +315,11 @@
defaultMobileIconGroup,
defaultDataSubId,
isDefaultConnectionFailed,
+ isForceHidden,
mobileConnectionsRepo.getRepoForSubId(subId),
)
+
+ companion object {
+ private const val LOGGING_PREFIX = "Intr"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a4b2abc..db585e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -91,10 +91,17 @@
}
}
+ launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
+
// Set the icon for the triangle
launch {
- viewModel.iconId.distinctUntilChanged().collect { iconId ->
- mobileDrawable.level = iconId
+ viewModel.icon.distinctUntilChanged().collect { icon ->
+ mobileDrawable.level =
+ SignalDrawable.getState(
+ icon.level,
+ icon.numberOfLevels,
+ icon.showExclamationMark,
+ )
}
}
@@ -148,8 +155,7 @@
return object : ModernStatusBarViewBinding {
override fun getShouldIconBeVisible(): Boolean {
- // If this view model exists, then the icon should be visible.
- return true
+ return viewModel.isVisible.value
}
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
new file mode 100644
index 0000000..16e1766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
+data class SignalIconModel(
+ val level: Int,
+ val numberOfLevels: Int,
+ val showExclamationMark: Boolean,
+) : Diffable<SignalIconModel> {
+ // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+ override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
+ if (prevVal.level != level) {
+ row.logChange(COL_LEVEL, level)
+ }
+ if (prevVal.numberOfLevels != numberOfLevels) {
+ row.logChange(COL_NUM_LEVELS, numberOfLevels)
+ }
+ if (prevVal.showExclamationMark != showExclamationMark) {
+ row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_LEVEL, level)
+ row.logChange(COL_NUM_LEVELS, numberOfLevels)
+ row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ }
+
+ companion object {
+ /** Creates a [SignalIconModel] representing an empty and invalidated state. */
+ fun createEmptyState(numberOfLevels: Int) =
+ SignalIconModel(level = 0, numberOfLevels, showExclamationMark = true)
+
+ private const val COL_LEVEL = "level"
+ private const val COL_NUM_LEVELS = "numLevels"
+ private const val COL_SHOW_EXCLAMATION = "showExclamation"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 9e2024a..0496278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -22,10 +22,11 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,14 +38,14 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
interface MobileIconViewModelCommon {
val subscriptionId: Int
- /** An int consumable by [SignalDrawable] for display */
- val iconId: Flow<Int>
+ /** True if this view should be visible at all. */
+ val isVisible: StateFlow<Boolean>
+ val icon: Flow<SignalIconModel>
val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
@@ -73,7 +74,7 @@
constructor(
override val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
- logger: ConnectivityPipelineLogger,
+ airplaneModeInteractor: AirplaneModeInteractor,
constants: ConnectivityConstants,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
@@ -81,8 +82,28 @@
private val showExclamationMark: Flow<Boolean> =
iconInteractor.isDefaultDataEnabled.mapLatest { !it }
- override val iconId: Flow<Int> = run {
- val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
+ override val isVisible: StateFlow<Boolean> =
+ if (!constants.hasDataCapabilities) {
+ flowOf(false)
+ } else {
+ combine(
+ airplaneModeInteractor.isAirplaneMode,
+ iconInteractor.isForceHidden,
+ ) { isAirplaneMode, isForceHidden ->
+ !isAirplaneMode && !isForceHidden
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "visible",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = run {
+ val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value)
combine(
iconInteractor.level,
iconInteractor.numberOfLevels,
@@ -90,16 +111,15 @@
iconInteractor.isInService,
) { level, numberOfLevels, showExclamationMark, isInService ->
if (!isInService) {
- SignalDrawable.getEmptyState(numberOfLevels)
+ SignalIconModel.createEmptyState(numberOfLevels)
} else {
- SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ SignalIconModel(level, numberOfLevels, showExclamationMark)
}
}
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "iconId",
+ columnPrefix = "icon",
initialValue = initial,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
@@ -124,14 +144,22 @@
private val showNetworkTypeIcon: Flow<Boolean> =
combine(
- iconInteractor.isDataConnected,
- iconInteractor.isDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
- iconInteractor.alwaysShowDataRatIcon,
- iconInteractor.isConnected,
- ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
- alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
- }
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ iconInteractor.alwaysShowDataRatIcon,
+ iconInteractor.isConnected,
+ ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
+ alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "showNetworkTypeIcon",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val networkTypeIcon: Flow<Icon?> =
combine(
@@ -149,14 +177,6 @@
}
}
.distinctUntilChanged()
- .onEach {
- // This is done as an onEach side effect since Icon is not Diffable (yet)
- iconInteractor.tableLogBuffer.logChange(
- prefix = "",
- columnName = "networkTypeIcon",
- value = it.toString(),
- )
- }
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val roaming: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 24370d2..185b668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -39,6 +41,7 @@
constructor(
val subscriptionIdsFlow: StateFlow<List<Int>>,
private val interactor: MobileIconsInteractor,
+ private val airplaneModeInteractor: AirplaneModeInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
@@ -56,7 +59,7 @@
?: MobileIconViewModel(
subId,
interactor.createMobileConnectionInteractorForSubId(subId),
- logger,
+ airplaneModeInteractor,
constants,
scope,
)
@@ -74,10 +77,12 @@
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
}
+ @SysUISingleton
class Factory
@Inject
constructor(
private val interactor: MobileIconsInteractor,
+ private val airplaneModeInteractor: AirplaneModeInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
@@ -87,6 +92,7 @@
return MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
+ airplaneModeInteractor,
logger,
constants,
scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 6796a94..45036969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -204,15 +204,6 @@
// TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
- fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
- buffer.log(
- SB_LOGGING_TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Filtered subscriptions updated: $str1" },
- )
- }
-
fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
buffer.log(
SB_LOGGING_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
new file mode 100644
index 0000000..577292f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.stylus
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class StylusUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "UiEvent for USI low battery notification shown")
+ STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN(1298),
+ @UiEvent(doc = "UiEvent for USI low battery notification clicked")
+ STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED(1299),
+ @UiEvent(doc = "UiEvent for USI low battery notification dismissed")
+ STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED(1300),
+ @UiEvent(doc = "UIEvent for Toast shown when stylus started charging")
+ STYLUS_STARTED_CHARGING(1302),
+ @UiEvent(doc = "UIEvent for Toast shown when stylus stopped charging")
+ STYLUS_STOPPED_CHARGING(1303);
+
+ override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 9050dad..89453ad 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -17,6 +17,7 @@
package com.android.systemui.stylus
import android.Manifest
+import android.app.ActivityManager
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
@@ -32,6 +33,7 @@
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -53,6 +55,7 @@
private val notificationManager: NotificationManagerCompat,
private val inputManager: InputManager,
@Background private val handler: Handler,
+ private val uiEventLogger: UiEventLogger,
) {
// These values must only be accessed on the handler.
@@ -79,12 +82,13 @@
fun refresh() {
handler.post refreshNotification@{
- if (!suppressed && !hasConnectedBluetoothStylus() && isBatteryBelowThreshold()) {
+ val batteryBelowThreshold = isBatteryBelowThreshold()
+ if (!suppressed && !hasConnectedBluetoothStylus() && batteryBelowThreshold) {
showOrUpdateNotification()
return@refreshNotification
}
- if (!isBatteryBelowThreshold()) {
+ if (!batteryBelowThreshold) {
// Reset suppression when stylus battery is recharged, so that the next time
// it reaches a low battery, the notification will show again.
suppressed = false
@@ -143,6 +147,7 @@
.setAutoCancel(true)
.build()
+ logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN)
notificationManager.notify(USI_NOTIFICATION_ID, notification)
}
@@ -168,8 +173,12 @@
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
- ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
+ ACTION_DISMISSED_LOW_BATTERY -> {
+ logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED)
+ updateSuppression(true)
+ }
ACTION_CLICKED_LOW_BATTERY -> {
+ logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED)
updateSuppression(true)
if (inputDeviceId == null) return
@@ -195,6 +204,15 @@
}
}
+ private fun logUiEvent(metricId: StylusUiEvent) {
+ uiEventLogger.logWithPosition(
+ metricId,
+ ActivityManager.getCurrentUser(),
+ context.packageName,
+ (batteryCapacity * 100.0).toInt()
+ )
+ }
+
companion object {
// Low battery threshold matches CrOS, see:
// https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41
@@ -203,10 +221,14 @@
private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage
@VisibleForTesting const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
+
@VisibleForTesting const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"
+
@VisibleForTesting
const val ACTION_STYLUS_USI_DETAILS = "com.android.settings.STYLUS_USI_DETAILS_SETTINGS"
+
@VisibleForTesting const val KEY_DEVICE_INPUT_ID = "device_input_id"
+
@VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
new file mode 100644
index 0000000..b41bca0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util.condition;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+
+import java.util.Set;
+
+/**
+ * {@link ConditionalCoreStartable} is a {@link com.android.systemui.CoreStartable} abstract
+ * implementation where conditions must be met before routines are executed.
+ */
+public abstract class ConditionalCoreStartable implements CoreStartable {
+ private final Monitor mMonitor;
+ private final Set<Condition> mConditionSet;
+ private Monitor.Subscription.Token mStartToken;
+ private Monitor.Subscription.Token mBootCompletedToken;
+
+ public ConditionalCoreStartable(Monitor monitor) {
+ this(monitor, null);
+ }
+
+ public ConditionalCoreStartable(Monitor monitor, Set<Condition> conditionSet) {
+ mMonitor = monitor;
+ mConditionSet = conditionSet;
+ }
+
+ @Override
+ public final void start() {
+ if (mConditionSet == null || mConditionSet.isEmpty()) {
+ onStart();
+ return;
+ }
+
+ mStartToken = mMonitor.addSubscription(
+ new Monitor.Subscription.Builder(allConditionsMet -> {
+ if (allConditionsMet) {
+ mMonitor.removeSubscription(mStartToken);
+ mStartToken = null;
+ onStart();
+ }
+ }).addConditions(mConditionSet)
+ .build());
+ }
+
+ protected abstract void onStart();
+
+ @Override
+ public final void onBootCompleted() {
+ if (mConditionSet == null || mConditionSet.isEmpty()) {
+ bootCompleted();
+ return;
+ }
+
+ mBootCompletedToken = mMonitor.addSubscription(
+ new Monitor.Subscription.Builder(allConditionsMet -> {
+ if (allConditionsMet) {
+ mMonitor.removeSubscription(mBootCompletedToken);
+ mBootCompletedToken = null;
+ bootCompleted();
+ }
+ }).addConditions(mConditionSet)
+ .build());
+ }
+
+ protected void bootCompleted() {
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 43a2017..f7fec80 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -58,7 +58,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
-import java.util.*
+import java.util.TimeZone
import java.util.concurrent.Executor
import org.mockito.Mockito.`when` as whenever
@@ -105,7 +105,9 @@
repository = FakeKeyguardRepository()
underTest = ClockEventController(
- KeyguardInteractor(repository = repository, commandQueue = commandQueue),
+ KeyguardInteractor(repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags),
KeyguardTransitionInteractor(repository = transitionRepository),
broadcastDispatcher,
batteryController,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 05bd1e4..3d0d036 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -159,7 +159,9 @@
mAuthRippleController,
mResources,
new KeyguardTransitionInteractor(mTransitionRepository),
- new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue),
+ new KeyguardInteractor(new FakeKeyguardRepository(),
+ mCommandQueue,
+ mFeatureFlags),
mFeatureFlags
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 9866163..bb037642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -21,12 +21,9 @@
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
-import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -106,7 +103,6 @@
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
- @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var udfpsKeyguardView: UdfpsKeyguardView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
@Mock private lateinit var featureFlags: FeatureFlags
@@ -121,18 +117,14 @@
@Before
fun setup() {
- context.orCreateTestableResources.addOverride(R.integer.config_udfpsEnrollProgressBar, 20)
whenever(inflater.inflate(R.layout.udfps_view, null, false))
.thenReturn(udfpsView)
- whenever(inflater.inflate(R.layout.udfps_enroll_view, null))
- .thenReturn(udfpsEnrollView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
whenever(inflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(udfpsKeyguardView)
whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null))
.thenReturn(mock(UdfpsFpmEmptyView::class.java))
- whenever(udfpsEnrollView.context).thenReturn(context)
}
private fun withReason(
@@ -162,37 +154,6 @@
}
@Test
- fun showUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { showUdfpsOverlay() }
-
- @Test
- fun showUdfpsOverlay_locate() = withReason(REASON_ENROLL_FIND_SENSOR) {
- showUdfpsOverlay(isEnrollUseCase = true)
- }
-
- @Test
- fun showUdfpsOverlay_locate_withEnrollmentUiRemoved() {
- Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
- withReason(REASON_ENROLL_FIND_SENSOR, isDebuggable = true) {
- showUdfpsOverlay(isEnrollUseCase = false)
- }
- Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
- }
-
- @Test
- fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) {
- showUdfpsOverlay(isEnrollUseCase = true)
- }
-
- @Test
- fun showUdfpsOverlay_enroll_withEnrollmentUiRemoved() {
- Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
- withReason(REASON_ENROLL_ENROLLING, isDebuggable = true) {
- showUdfpsOverlay(isEnrollUseCase = false)
- }
- Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
- }
-
- @Test
fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
@@ -281,7 +242,7 @@
}
}
- private fun showUdfpsOverlay(isEnrollUseCase: Boolean = false) {
+ private fun showUdfpsOverlay() {
val didShow = controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
@@ -293,12 +254,6 @@
assertThat(controllerOverlay.isShowing).isTrue()
assertThat(controllerOverlay.isHiding).isFalse()
assertThat(controllerOverlay.overlayView).isNotNull()
- if (isEnrollUseCase) {
- verify(udfpsEnrollView).updateSensorLocation(eq(overlayParams.sensorBounds))
- assertThat(controllerOverlay.enrollHelper).isNotNull()
- } else {
- assertThat(controllerOverlay.enrollHelper).isNull()
- }
}
@Test
@@ -311,12 +266,6 @@
fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() }
@Test
- fun hideUdfpsOverlay_locate() = withReason(REASON_ENROLL_FIND_SENSOR) { hideUdfpsOverlay() }
-
- @Test
- fun hideUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) { hideUdfpsOverlay() }
-
- @Test
fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() }
private fun hideUdfpsOverlay() {
@@ -346,44 +295,6 @@
}
@Test
- fun forwardEnrollProgressEvents() = withReason(REASON_ENROLL_ENROLLING) {
- controllerOverlay.show(udfpsController, overlayParams)
-
- with(EnrollListener(controllerOverlay)) {
- controllerOverlay.onEnrollmentProgress(/* remaining */20)
- controllerOverlay.onAcquiredGood()
- assertThat(progress).isTrue()
- assertThat(help).isFalse()
- assertThat(acquired).isFalse()
- }
- }
-
- @Test
- fun forwardEnrollHelpEvents() = withReason(REASON_ENROLL_ENROLLING) {
- controllerOverlay.show(udfpsController, overlayParams)
-
- with(EnrollListener(controllerOverlay)) {
- controllerOverlay.onEnrollmentHelp()
- assertThat(progress).isFalse()
- assertThat(help).isTrue()
- assertThat(acquired).isFalse()
- }
- }
-
- @Test
- fun forwardEnrollAcquiredEvents() = withReason(REASON_ENROLL_ENROLLING) {
- controllerOverlay.show(udfpsController, overlayParams)
-
- with(EnrollListener(controllerOverlay)) {
- controllerOverlay.onEnrollmentProgress(/* remaining */ 1)
- controllerOverlay.onAcquiredGood()
- assertThat(progress).isTrue()
- assertThat(help).isFalse()
- assertThat(acquired).isTrue()
- }
- }
-
- @Test
fun cancels() = withReason(REASON_AUTH_BP) {
controllerOverlay.cancel()
verify(controllerCallback).onUserCanceled()
@@ -404,27 +315,3 @@
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
}
}
-
-private class EnrollListener(
- overlay: UdfpsControllerOverlay,
- var progress: Boolean = false,
- var help: Boolean = false,
- var acquired: Boolean = false
-) : UdfpsEnrollHelper.Listener {
-
- init {
- overlay.enrollHelper!!.setListener(this)
- }
-
- override fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
- progress = true
- }
-
- override fun onEnrollmentHelp(remaining: Int, totalSteps: Int) {
- help = true
- }
-
- override fun onLastStepAcquired() {
- acquired = true
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
deleted file mode 100644
index 60a0258..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
+++ /dev/null
@@ -1,50 +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.biometrics;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.res.Configuration;
-import android.graphics.Color;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class UdfpsEnrollViewTest extends SysuiTestCase {
-
- private static String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3";
- private static String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1";
-
- @Test
- public void fingerprintUdfpsEnroll_usesCorrectThemeCheckmarkFillColor() {
- final Configuration config = mContext.getResources().getConfiguration();
- final boolean isDarkThemeOn = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
- final int currentColor = mContext.getColor(R.color.udfps_enroll_progress);
-
- assertThat(currentColor).isEqualTo(Color.parseColor(isDarkThemeOn
- ? ENROLL_PROGRESS_COLOR_DARK : ENROLL_PROGRESS_COLOR_LIGHT));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index fb54d6d..4415033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -157,25 +157,28 @@
dumpManager = mock(),
userHandle = UserHandle.SYSTEM,
)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
+ set(Flags.REVAMPED_WALLPAPER_UI, true)
+ set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
underTest.interactor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
KeyguardInteractor(
repository = FakeKeyguardRepository(),
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
registry = mock(),
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
- set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
- set(Flags.REVAMPED_WALLPAPER_UI, true)
- set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
- },
+ featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 2290676..c3b0e5226 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -38,6 +38,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -66,6 +67,8 @@
private KeyguardIndicationTextView mView;
@Mock
private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private KeyguardLogger mLogger;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@@ -77,7 +80,7 @@
MockitoAnnotations.initMocks(this);
when(mView.getTextColors()).thenReturn(ColorStateList.valueOf(Color.WHITE));
mController = new KeyguardIndicationRotateTextViewController(mView, mExecutor,
- mStatusBarStateController);
+ mStatusBarStateController, mLogger);
mController.onViewAttached();
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 68d13d3..d938243 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,30 +18,34 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.content.Context
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.settings.DisplayTracker
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.CommandQueue.Callbacks
-import com.android.systemui.util.mockito.argumentCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
+import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- @Mock private lateinit var commandQueue: CommandQueue
+ private lateinit var commandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var testScope: TestScope
private lateinit var underTest: KeyguardInteractor
private lateinit var repository: FakeKeyguardRepository
@@ -49,38 +53,134 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+ commandQueue = FakeCommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java))
+ testScope = TestScope()
repository = FakeKeyguardRepository()
- underTest = KeyguardInteractor(repository, commandQueue)
+ underTest = KeyguardInteractor(repository, commandQueue, featureFlags)
}
@Test
- fun onCameraLaunchDetected() = runTest {
- val flow = underTest.onCameraLaunchDetected
- var cameraLaunchSource = collectLastValue(flow)
- runCurrent()
+ fun onCameraLaunchDetected() =
+ testScope.runTest {
+ val flow = underTest.onCameraLaunchDetected
+ var cameraLaunchSource = collectLastValue(flow)
+ runCurrent()
- val captor = argumentCaptor<CommandQueue.Callbacks>()
- verify(commandQueue).addCallback(captor.capture())
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
- captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
- captor.value.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
- captor.value.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER
- )
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
+ )
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
- captor.value.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
- )
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
+ flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) }
+ }
- flow.onCompletion { verify(commandQueue).removeCallback(captor.value) }
+ @Test
+ fun testKeyguardGuardVisibilityStopsSecureCamera() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ }
+
+ assertThat(secureCameraActive()).isTrue()
+
+ // Keyguard is showing but occluded
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(true)
+ assertThat(secureCameraActive()).isTrue()
+
+ // Keyguard is showing and not occluded
+ repository.setKeyguardOccluded(false)
+ assertThat(secureCameraActive()).isFalse()
+ }
+
+ @Test
+ fun testBouncerShowingResetsSecureCameraState() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ }
+ assertThat(secureCameraActive()).isTrue()
+
+ // Keyguard is showing and not occluded
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(true)
+ assertThat(secureCameraActive()).isTrue()
+
+ repository.setBouncerShowing(true)
+ assertThat(secureCameraActive()).isFalse()
+ }
+
+ @Test
+ fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
+ var isVisible = collectLastValue(underTest.isKeyguardVisible)
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(false)
+
+ assertThat(isVisible()).isTrue()
+
+ repository.setKeyguardOccluded(true)
+ assertThat(isVisible()).isFalse()
+
+ repository.setKeyguardShowing(false)
+ repository.setKeyguardOccluded(true)
+ assertThat(isVisible()).isFalse()
}
+
+ @Test
+ fun secureCameraIsNotActiveWhenNoCameraLaunchEventHasBeenFiredYet() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ assertThat(secureCameraActive()).isFalse()
+ }
+}
+
+class FakeCommandQueue(val context: Context, val displayTracker: DisplayTracker) :
+ CommandQueue(context, displayTracker) {
+ private val callbacks = mutableListOf<Callbacks>()
+
+ override fun addCallback(callback: Callbacks) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: Callbacks) {
+ callbacks.remove(callback)
+ }
+
+ fun doForEachCallback(func: (callback: Callbacks) -> Unit) {
+ callbacks.forEach { func(it) }
+ }
+
+ fun callbackCount(): Int = callbacks.size
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 43287b0..240af7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -286,12 +286,18 @@
dumpManager = mock(),
userHandle = UserHandle.SYSTEM,
)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
KeyguardInteractor(
repository = FakeKeyguardRepository(),
- commandQueue = commandQueue
+ commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
registry =
FakeKeyguardQuickAffordanceRegistry(
@@ -311,10 +317,7 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
- },
+ featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b75a15d..8cff0ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -150,12 +150,17 @@
featureFlags =
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
- KeyguardInteractor(repository = repository, commandQueue = commandQueue),
+ KeyguardInteractor(
+ repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags
+ ),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 702f3763..46e4679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,6 +21,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
@@ -92,10 +94,12 @@
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
+ val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
@@ -105,7 +109,8 @@
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -114,7 +119,8 @@
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -123,7 +129,8 @@
fromGoneTransitionInteractor =
FromGoneTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -132,7 +139,8 @@
fromDozingTransitionInteractor =
FromDozingTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -141,7 +149,8 @@
fromOccludedTransitionInteractor =
FromOccludedTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -149,7 +158,7 @@
}
@Test
- fun `DREAMING to LOCKSCREEN`() =
+ fun `DREAMING to LOCKSCREEN - dreaming state changes first`() =
testScope.runTest {
// GIVEN a device is dreaming and occluded
keyguardRepository.setDreamingWithOverlay(true)
@@ -179,9 +188,59 @@
)
// AND dreaming has stopped
keyguardRepository.setDreamingWithOverlay(false)
+ advanceUntilIdle()
+ // AND then occluded has stopped
+ keyguardRepository.setKeyguardOccluded(false)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to BOUNCER should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `DREAMING to LOCKSCREEN - occluded state changes first`() =
+ testScope.runTest {
+ // GIVEN a device is dreaming and occluded
+ keyguardRepository.setDreamingWithOverlay(true)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN doze is complete
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
// AND occluded has stopped
keyguardRepository.setKeyguardOccluded(false)
advanceUntilIdle()
+ // AND then dreaming has stopped
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceUntilIdle()
val info =
withArgCaptor<TransitionInfo> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 4b04b7b..03a347e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -125,9 +125,18 @@
),
)
repository = FakeKeyguardRepository()
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
val keyguardInteractor =
- KeyguardInteractor(repository = repository, commandQueue = commandQueue)
+ KeyguardInteractor(
+ repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags,
+ )
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
@@ -191,10 +200,7 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
- },
+ featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
index 3b5e6b9..d1744c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
@@ -276,6 +276,52 @@
}
@Test
+ fun intNullable_logsNull() =
+ testScope.runTest {
+ systemClock.setCurrentTimeMillis(100L)
+ val flow = flow {
+ for (int in listOf(null, 6, null, 8)) {
+ systemClock.advanceTime(100L)
+ emit(int)
+ }
+ }
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = 1234,
+ )
+
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "1234"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "6"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "8"
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun int_logsUpdates() =
testScope.runTest {
systemClock.setCurrentTimeMillis(100L)
@@ -1030,6 +1076,246 @@
job.cancel()
}
+ // ---- Flow<List<T>> tests ----
+
+ @Test
+ fun list_doesNotLogWhenNotCollected() {
+ val flow = flowOf(listOf(5), listOf(6), listOf(7))
+
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf(1234),
+ )
+
+ val logs = dumpLog()
+ assertThat(logs).doesNotContain(COLUMN_PREFIX)
+ assertThat(logs).doesNotContain(COLUMN_NAME)
+ assertThat(logs).doesNotContain("1234")
+ }
+
+ @Test
+ fun list_logsInitialWhenCollected() =
+ testScope.runTest {
+ val flow = flowOf(listOf(5), listOf(6), listOf(7))
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf(1234),
+ )
+
+ systemClock.setCurrentTimeMillis(3000L)
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(3000L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(1234).toString()
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun list_logsUpdates() =
+ testScope.runTest {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val listItems =
+ listOf(listOf("val1", "val2"), listOf("val3"), listOf("val4", "val5", "val6"))
+ val flow = flow {
+ for (list in listItems) {
+ systemClock.advanceTime(100L)
+ emit(list)
+ }
+ }
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf("val0", "val00"),
+ )
+
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val0", "val00").toString()
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1", "val2").toString()
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val3").toString()
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(400L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val4", "val5", "val6").toString()
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun list_doesNotLogIfSameValue() =
+ testScope.runTest {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val listItems =
+ listOf(
+ listOf("val0", "val00"),
+ listOf("val1"),
+ listOf("val1"),
+ listOf("val1", "val2"),
+ )
+ val flow = flow {
+ for (bool in listItems) {
+ systemClock.advanceTime(100L)
+ emit(bool)
+ }
+ }
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf("val0", "val00"),
+ )
+
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+
+ val expected1 =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val0", "val00").toString()
+ val expected3 =
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1").toString()
+ val expected5 =
+ TABLE_LOG_DATE_FORMAT.format(500L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1", "val2").toString()
+ assertThat(logs).contains(expected1)
+ assertThat(logs).contains(expected3)
+ assertThat(logs).contains(expected5)
+
+ val unexpected2 =
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val0", "val00")
+ val unexpected4 =
+ TABLE_LOG_DATE_FORMAT.format(400L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1")
+ assertThat(logs).doesNotContain(unexpected2)
+ assertThat(logs).doesNotContain(unexpected4)
+ job.cancel()
+ }
+
+ @Test
+ fun list_worksForStateFlows() =
+ testScope.runTest {
+ val flow = MutableStateFlow(listOf(1111))
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf(1111),
+ )
+
+ systemClock.setCurrentTimeMillis(50L)
+ val job = launch { flowWithLogging.collect() }
+ assertThat(dumpLog())
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(50L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(1111).toString()
+ )
+
+ systemClock.setCurrentTimeMillis(100L)
+ flow.emit(listOf(2222, 3333))
+ assertThat(dumpLog())
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(2222, 3333).toString()
+ )
+
+ systemClock.setCurrentTimeMillis(200L)
+ flow.emit(listOf(3333, 4444))
+ assertThat(dumpLog())
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(3333, 4444).toString()
+ )
+
+ // Doesn't log duplicates
+ systemClock.setCurrentTimeMillis(300L)
+ flow.emit(listOf(3333, 4444))
+ assertThat(dumpLog())
+ .doesNotContain(
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(3333, 4444).toString()
+ )
+
+ job.cancel()
+ }
+
private fun dumpLog(): String {
val outputWriter = StringWriter()
tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index 432764a..c7f3fa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -36,6 +36,17 @@
}
@Test
+ fun setString_null() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(null as String?)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
fun setBoolean_isBoolean() {
val underTest = TableChange()
@@ -58,6 +69,17 @@
}
@Test
+ fun setInt_null() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(null as Int?)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
fun setThenReset_isEmpty() {
val underTest = TableChange()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 31866a8..9ecc63c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -36,6 +36,7 @@
import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.FeatureFlagUtils;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -171,6 +172,8 @@
mLocalBluetoothLeBroadcast);
when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mMediaDevice.isBLEDevice()).thenReturn(false);
assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.GONE);
@@ -184,6 +187,62 @@
}
@Test
+ public void isBroadcastSupported_flagOnAndConnectBleDevice_returnsTrue() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+ when(mMediaDevice.isBLEDevice()).thenReturn(true);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue();
+ }
+
+ @Test
+ public void isBroadcastSupported_flagOnAndNoBleDevice_returnsFalse() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+ when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isFalse();
+ }
+
+ @Test
+ public void isBroadcastSupported_notSupportBroadcastAndflagOn_returnsFalse() {
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isFalse();
+ }
+
+ @Test
+ public void isBroadcastSupported_flagOffAndConnectToBleDevice_returnsTrue() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, false);
+ when(mMediaDevice.isBLEDevice()).thenReturn(true);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue();
+ }
+
+ @Test
+ public void isBroadcastSupported_flagOffAndNoBleDevice_returnsTrue() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, false);
+ when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue();
+ }
+
+ @Test
public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 1042ea7..4977775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -24,6 +24,8 @@
private val taskListProvider = TestRecentTaskListProvider()
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
+ private val callerPackageName = "com.test.caller"
+ private val callerComponentName = ComponentName(callerPackageName, "Caller")
private val hostUserHandle = UserHandle.of(123)
private val otherUserHandle = UserHandle.of(456)
@@ -31,14 +33,16 @@
private val view: MediaProjectionAppSelectorView = mock()
private val featureFlags: FeatureFlags = mock()
- private val controller = MediaProjectionAppSelectorController(
- taskListProvider,
- view,
- featureFlags,
- hostUserHandle,
- scope,
- appSelectorComponentName
- )
+ private val controller =
+ MediaProjectionAppSelectorController(
+ taskListProvider,
+ view,
+ featureFlags,
+ hostUserHandle,
+ scope,
+ appSelectorComponentName,
+ callerPackageName
+ )
@Test
fun initNoRecentTasks_bindsEmptyList() {
@@ -51,104 +55,87 @@
@Test
fun initOneRecentTask_bindsList() {
- taskListProvider.tasks = listOf(
- createRecentTask(taskId = 1)
- )
+ taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
controller.init()
- verify(view).bind(
- listOf(
- createRecentTask(taskId = 1)
- )
- )
+ verify(view).bind(listOf(createRecentTask(taskId = 1)))
}
@Test
fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
- val tasks = listOf(
- createRecentTask(taskId = 1),
- createRecentTask(taskId = 2),
- createRecentTask(taskId = 3),
- )
- taskListProvider.tasks = tasks
-
- controller.init()
-
- verify(view).bind(
+ val tasks =
listOf(
createRecentTask(taskId = 1),
createRecentTask(taskId = 2),
createRecentTask(taskId = 3),
)
- )
- }
-
- @Test
- fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
- val tasks = listOf(
- createRecentTask(taskId = 1),
- createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
- createRecentTask(taskId = 3),
- createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
- createRecentTask(taskId = 5),
- )
taskListProvider.tasks = tasks
controller.init()
- verify(view).bind(
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2),
+ createRecentTask(taskId = 3),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_removeAppSelector() {
+ val tasks =
listOf(
createRecentTask(taskId = 1),
- createRecentTask(taskId = 3),
- createRecentTask(taskId = 5),
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
- createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
)
- )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_bindsCallerTasksAtTheEnd() {
+ val tasks =
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2, topActivityComponent = callerComponentName),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ createRecentTask(taskId = 2, topActivityComponent = callerComponentName),
+ )
+ )
}
@Test
fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
givenEnterprisePoliciesFeatureFlag(enabled = false)
- val tasks = listOf(
- createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
- )
- taskListProvider.tasks = tasks
-
- controller.init()
-
- verify(view).bind(
- listOf(
- createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
- )
- )
- }
-
- @Test
- fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
- givenEnterprisePoliciesFeatureFlag(enabled = true)
-
- val tasks = listOf(
- createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
- )
- taskListProvider.tasks = tasks
-
- controller.init()
-
- // TODO(b/233348916) should filter depending on the policies
- verify(view).bind(
+ val tasks =
listOf(
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
@@ -156,7 +143,47 @@
createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
)
- )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
+ givenEnterprisePoliciesFeatureFlag(enabled = true)
+
+ val tasks =
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ // TODO(b/233348916) should filter depending on the policies
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
}
private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
@@ -183,6 +210,5 @@
var tasks: List<RecentTask> = emptyList()
override suspend fun loadRecentTasks(): List<RecentTask> = tasks
-
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java
new file mode 100644
index 0000000..2293fc5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.process.condition;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.process.ProcessWrapper;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class UserProcessConditionTest extends SysuiTestCase {
+ @Mock
+ UserTracker mUserTracker;
+
+ @Mock
+ ProcessWrapper mProcessWrapper;
+
+ @Mock
+ Monitor.Callback mCallback;
+
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Verifies condition reports false when tracker reports a different user id than the
+ * identifier from the process handle.
+ */
+ @Test
+ public void testConditionFailsWithDifferentIds() {
+
+ final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
+ when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
+ when(mUserTracker.getUserId()).thenReturn(1);
+
+ final Monitor monitor = new Monitor(mExecutor);
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(condition)
+ .build());
+
+ mExecutor.runAllReady();
+
+ verify(mCallback).onConditionsChanged(false);
+ }
+
+ /**
+ * Verifies condition reports false when tracker reports a different user id than the
+ * identifier from the process handle.
+ */
+ @Test
+ public void testConditionSucceedsWithSameIds() {
+
+ final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
+ when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
+ when(mUserTracker.getUserId()).thenReturn(0);
+
+ final Monitor monitor = new Monitor(mExecutor);
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(condition)
+ .build());
+
+ mExecutor.runAllReady();
+
+ verify(mCallback).onConditionsChanged(true);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index 7693fee..9eccbb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -471,4 +471,142 @@
mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
+
+ /**
+ * Ensures that the result of a condition being true leads to its nested condition being
+ * activated.
+ */
+ @Test
+ public void testNestedCondition() {
+ mCondition1.fakeUpdateCondition(false);
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mCondition2.fakeUpdateCondition(false);
+
+ // Create a nested condition
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(
+ new Monitor.Subscription.Builder(callback)
+ .addCondition(mCondition2)
+ .build())
+ .addCondition(mCondition1)
+ .build());
+
+ mExecutor.runAllReady();
+
+ // Ensure the nested condition callback is not called at all.
+ verify(callback, never()).onActiveChanged(anyBoolean());
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+
+ // Update the inner condition to true and ensure that the nested condition is not triggered.
+ mCondition2.fakeUpdateCondition(true);
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ mCondition2.fakeUpdateCondition(false);
+
+ // Set outer condition and make sure the inner condition becomes active and reports that
+ // conditions aren't met
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(true));
+ verify(callback).onConditionsChanged(eq(false));
+
+ Mockito.clearInvocations(callback);
+
+ // Update the inner condition and make sure the callback is updated.
+ mCondition2.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(true);
+
+ Mockito.clearInvocations(callback);
+ // Invalidate outer condition and make sure callback is informed, but the last state is
+ // not affected.
+ mCondition1.fakeUpdateCondition(false);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(false));
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ }
+
+ /**
+ * Ensures a subscription is predicated on its precondition.
+ */
+ @Test
+ public void testPrecondition() {
+ mCondition1.fakeUpdateCondition(false);
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mCondition2.fakeUpdateCondition(false);
+
+ // Create a nested condition
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(callback)
+ .addPrecondition(mCondition1)
+ .addCondition(mCondition2)
+ .build());
+
+ mExecutor.runAllReady();
+
+ // Ensure the nested condition callback is not called at all.
+ verify(callback, never()).onActiveChanged(anyBoolean());
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+
+ // Update the condition to true and ensure that the nested condition is not triggered.
+ mCondition2.fakeUpdateCondition(true);
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ mCondition2.fakeUpdateCondition(false);
+
+ // Set precondition and make sure the inner condition becomes active and reports that
+ // conditions aren't met
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(true));
+ verify(callback).onConditionsChanged(eq(false));
+
+ Mockito.clearInvocations(callback);
+
+ // Update the condition and make sure the callback is updated.
+ mCondition2.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(true);
+
+ Mockito.clearInvocations(callback);
+ // Invalidate precondition and make sure callback is informed, but the last state is
+ // not affected.
+ mCondition1.fakeUpdateCondition(false);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(false));
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ }
+
+ /**
+ * Ensure preconditions are applied to every subscription added to a monitor.
+ */
+ @Test
+ public void testPreconditionMonitor() {
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mCondition2.fakeUpdateCondition(true);
+ final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1)));
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback)
+ .addCondition(mCondition2)
+ .build());
+
+ mExecutor.runAllReady();
+
+ verify(callback, never()).onActiveChanged(anyBoolean());
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(true));
+ verify(callback).onConditionsChanged(eq(true));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 6fb6893..8aaa57f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -23,6 +23,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -33,7 +35,10 @@
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +51,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -87,6 +94,61 @@
testCallOnAdd_forManager(manager);
}
+ @Test
+ public void testRemoveIcon_ignoredForNewPipeline() {
+ IconManager manager = mock(IconManager.class);
+
+ // GIVEN the new pipeline is on
+ StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
+ when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
+
+ StatusBarIconController iconController = new StatusBarIconControllerImpl(
+ mContext,
+ mock(CommandQueue.class),
+ mock(DemoModeController.class),
+ mock(ConfigurationController.class),
+ mock(TunerService.class),
+ mock(DumpManager.class),
+ mock(StatusBarIconList.class),
+ flags
+ );
+
+ iconController.addIconGroup(manager);
+
+ // WHEN a request to remove a new icon is sent
+ iconController.removeIcon("test_icon", 0);
+
+ // THEN it is not removed for those icons
+ verify(manager, never()).onRemoveIcon(anyInt());
+ }
+
+ @Test
+ public void testRemoveAllIconsForSlot_ignoredForNewPipeline() {
+ IconManager manager = mock(IconManager.class);
+
+ // GIVEN the new pipeline is on
+ StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
+ when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
+
+ StatusBarIconController iconController = new StatusBarIconControllerImpl(
+ mContext,
+ mock(CommandQueue.class),
+ mock(DemoModeController.class),
+ mock(ConfigurationController.class),
+ mock(TunerService.class),
+ mock(DumpManager.class),
+ mock(StatusBarIconList.class),
+ flags
+ );
+
+ iconController.addIconGroup(manager);
+
+ // WHEN a request to remove a new icon is sent
+ iconController.removeAllIconsForSlot("test_icon");
+
+ // THEN it is not removed for those icons
+ verify(manager, never()).onRemoveIcon(anyInt());
+ }
private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
StatusBarIconHolder holder = holderForType(TYPE_ICON);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 96cca44..4da2104 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
@@ -81,6 +82,7 @@
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var demoModeController: DemoModeController
@Mock private lateinit var dumpManager: DumpManager
@@ -114,6 +116,7 @@
subscriptionManager,
telephonyManager,
logger,
+ summaryLogger,
mobileMappings,
fakeBroadcastDispatcher,
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 24b9f7d..da208a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,20 +16,32 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -52,9 +64,10 @@
class FullMobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: FullMobileConnectionRepository
+ private val systemClock = FakeSystemClock()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
- private val tableLogBuffer = mock<TableLogBuffer>()
+ private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
@@ -347,8 +360,214 @@
.isSameInstanceAs(connection1Repeat.tableLogBuffer)
}
- // TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo
- // implements logging).
+ @Test
+ fun connectionInfo_logging_notCarrierMerged_getsUpdates() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager = mock<TelephonyManager>()
+ createRealMobileRepo(telephonyManager)
+ createRealCarrierMergedRepo(FakeWifiRepository())
+
+ initializeRepo(startingIsCarrierMerged = false)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ // WHEN we set up some mobile connection info
+ val serviceState = ServiceState()
+ serviceState.setOperatorName("longName", "OpTypical", "1")
+ serviceState.isEmergencyOnly = false
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState)
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false")
+
+ // WHEN we update mobile connection info
+ val serviceState2 = ServiceState()
+ serviceState2.setOperatorName("longName", "OpDiff", "1")
+ serviceState2.isEmergencyOnly = true
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState2)
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
+
+ job.cancel()
+ }
+
+ @Test
+ fun connectionInfo_logging_carrierMerged_getsUpdates() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ createRealMobileRepo(mock())
+ val wifiRepository = FakeWifiRepository()
+ createRealCarrierMergedRepo(wifiRepository)
+
+ initializeRepo(startingIsCarrierMerged = true)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ // WHEN we set up carrier merged info
+ val networkId = 2
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 3,
+ )
+ )
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN we update the info
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 1,
+ )
+ )
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ job.cancel()
+ }
+
+ @Test
+ fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager = mock<TelephonyManager>()
+ createRealMobileRepo(telephonyManager)
+
+ val wifiRepository = FakeWifiRepository()
+ createRealCarrierMergedRepo(wifiRepository)
+
+ initializeRepo(startingIsCarrierMerged = false)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ // WHEN we set up some mobile connection info
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.level).thenReturn(1)
+
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN isCarrierMerged is set to true
+ val networkId = 2
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 3,
+ )
+ )
+ underTest.setIsCarrierMerged(true)
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN the carrier merge network is updated
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 4,
+ )
+ )
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ // WHEN isCarrierMerged is set to false
+ underTest.setIsCarrierMerged(false)
+
+ // THEN the typical info is logged
+ // Note: Since our first logs also had the typical info, we need to search the log
+ // contents for after our carrier merged level log.
+ val fullBuffer = dumpBuffer()
+ val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4")
+ val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex)
+ assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN the normal network is updated
+ val newMobileInfo =
+ MobileConnectionModel(
+ operatorAlphaShort = "Mobile Operator 2",
+ primaryLevel = 0,
+ )
+ mobileRepo.setConnectionInfo(newMobileInfo)
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
+
+ job.cancel()
+ }
+
+ @Test
+ fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager = mock<TelephonyManager>()
+ createRealMobileRepo(telephonyManager)
+
+ val wifiRepository = FakeWifiRepository()
+ createRealCarrierMergedRepo(wifiRepository)
+
+ // WHEN isCarrierMerged = false
+ initializeRepo(startingIsCarrierMerged = false)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.level).thenReturn(1)
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+
+ // THEN updates to the carrier merged level aren't logged
+ val networkId = 2
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 4,
+ )
+ )
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 3,
+ )
+ )
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN isCarrierMerged is set to true
+ underTest.setIsCarrierMerged(true)
+
+ // THEN updates to the normal level aren't logged
+ whenever(signalStrength.level).thenReturn(5)
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5")
+
+ whenever(signalStrength.level).thenReturn(6)
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6")
+
+ job.cancel()
+ }
private fun initializeRepo(startingIsCarrierMerged: Boolean) {
underTest =
@@ -364,9 +583,68 @@
)
}
+ private fun createRealMobileRepo(
+ telephonyManager: TelephonyManager,
+ ): MobileConnectionRepositoryImpl {
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+
+ val realRepo =
+ MobileConnectionRepositoryImpl(
+ context,
+ SUB_ID,
+ defaultNetworkName = NetworkNameModel.Default("default"),
+ networkNameSeparator = SEP,
+ telephonyManager,
+ systemUiCarrierConfig = mock(),
+ fakeBroadcastDispatcher,
+ mobileMappingsProxy = mock(),
+ testDispatcher,
+ logger = mock(),
+ tableLogBuffer,
+ testScope.backgroundScope,
+ )
+ whenever(
+ mobileFactory.build(
+ eq(SUB_ID),
+ any(),
+ eq(DEFAULT_NAME),
+ eq(SEP),
+ )
+ )
+ .thenReturn(realRepo)
+
+ return realRepo
+ }
+
+ private fun createRealCarrierMergedRepo(
+ wifiRepository: FakeWifiRepository,
+ ): CarrierMergedConnectionRepository {
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setIsWifiDefault(true)
+ val realRepo =
+ CarrierMergedConnectionRepository(
+ SUB_ID,
+ tableLogBuffer,
+ defaultNetworkName = NetworkNameModel.Default("default"),
+ testScope.backgroundScope,
+ wifiRepository,
+ )
+ whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME)))
+ .thenReturn(realRepo)
+
+ return realRepo
+ }
+
+ private fun dumpBuffer(): String {
+ val outputWriter = StringWriter()
+ tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
+ return outputWriter.toString()
+ }
+
private companion object {
const val SUB_ID = 42
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
private const val SEP = "-"
+ private const val BUFFER_SEPARATOR = "|"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3f36bc1..1a5cc9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -71,7 +71,6 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -86,7 +85,6 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -670,16 +668,8 @@
job.cancel()
}
- private fun getTelephonyCallbacks(): List<TelephonyCallback> {
- val callbackCaptor = argumentCaptor<TelephonyCallback>()
- Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
- return callbackCaptor.allValues
- }
-
private inline fun <reified T> getTelephonyCallbackForType(): T {
- val cbs = getTelephonyCallbacks().filterIsInstance<T>()
- assertThat(cbs.size).isEqualTo(1)
- return cbs[0]
+ return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
/** Convenience constructor for SignalStrength */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b73348c..fef0981 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -84,6 +84,7 @@
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
private val mobileMappings = FakeMobileMappingsProxy()
@@ -157,6 +158,7 @@
subscriptionManager,
telephonyManager,
logger,
+ summaryLogger,
mobileMappings,
fakeBroadcastDispatcher,
context,
@@ -616,6 +618,7 @@
subscriptionManager,
telephonyManager,
logger,
+ summaryLogger,
mobileMappings,
fakeBroadcastDispatcher,
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
new file mode 100644
index 0000000..621f793
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import org.mockito.Mockito.verify
+
+/** Helper methods for telephony-related callbacks for mobile tests. */
+object MobileTelephonyHelpers {
+ fun getTelephonyCallbacks(mockTelephonyManager: TelephonyManager): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(mockTelephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
+ val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 7aeaa48..b9eda717dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -71,6 +71,8 @@
private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
override val numberOfLevels = _numberOfLevels
+ override val isForceHidden = MutableStateFlow(false)
+
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
_iconGroup.value = group
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 172755c..2699316 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -73,6 +73,8 @@
private val _isUserSetup = MutableStateFlow(true)
override val isUserSetup = _isUserSetup
+ override val isForceHidden = MutableStateFlow(false)
+
/** Always returns a new fake interactor */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
return FakeMobileIconInteractor(tableLogBuffer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index c42aba5..f87f651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -66,6 +66,7 @@
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.defaultDataSubId,
mobileIconsInteractor.isDefaultConnectionFailed,
+ mobileIconsInteractor.isForceHidden,
connectionRepository,
)
}
@@ -550,6 +551,21 @@
job.cancel()
}
+ @Test
+ fun isForceHidden_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.isForceHidden.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.isForceHidden.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index bd24922..f8a9783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -50,6 +52,7 @@
@SmallTest
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
+ private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
@@ -63,6 +66,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ connectivityRepository = FakeConnectivityRepository()
+
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap(
mapOf(
@@ -79,6 +84,8 @@
connectionsRepository,
carrierConfigTracker,
logger = mock(),
+ tableLogger = mock(),
+ connectivityRepository,
userSetupRepository,
testScope.backgroundScope,
)
@@ -609,6 +616,32 @@
job.cancel()
}
+ @Test
+ fun isForceHidden_repoHasMobileHidden_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index a2c1209..e68a397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -29,12 +29,14 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,31 +60,37 @@
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
- @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
+ private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+ private lateinit var viewModelCommon: MobileIconViewModel
private lateinit var viewModel: LocationBasedMobileViewModel
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ // This line was necessary to make the onDarkChanged and setStaticDrawableColor tests pass.
+ // But, it maybe *shouldn't* be necessary.
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+
testableLooper = TestableLooper.get(this)
- val interactor = FakeMobileIconInteractor(tableLogBuffer)
-
- val viewModelCommon =
- MobileIconViewModel(
- subscriptionId = 1,
- interactor,
- logger,
- constants,
- testScope.backgroundScope,
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ FakeConnectivityRepository(),
)
- viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+
+ interactor = FakeMobileIconInteractor(tableLogBuffer)
+ createViewModel()
}
// Note: The following tests are more like integration tests, since they stand up a full
- // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+ // [MobileIconViewModel] and test the interactions between the view, view-binder, and
+ // view-model.
@Test
fun setVisibleState_icon_iconShownDotHidden() {
@@ -130,7 +138,25 @@
}
@Test
- fun isIconVisible_alwaysTrue() {
+ fun isIconVisible_noData_outputsFalse() {
+ whenever(constants.hasDataCapabilities).thenReturn(false)
+ createViewModel()
+
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isFalse()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_hasData_outputsTrue() {
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+ createViewModel()
+
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
@@ -142,6 +168,34 @@
}
@Test
+ fun isIconVisible_notAirplaneMode_outputsTrue() {
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isTrue()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_airplaneMode_outputsTrue() {
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isFalse()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
@@ -184,6 +238,18 @@
private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot)
}
+
+ private fun createViewModel() {
+ viewModelCommon =
+ MobileIconViewModel(
+ subscriptionId = 1,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+ viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+ }
}
private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index c960a06..f983030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -21,10 +21,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -46,8 +49,8 @@
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
- @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -57,6 +60,11 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ )
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
@@ -68,7 +76,13 @@
isDataConnected.value = true
}
commonImpl =
- MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+ MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -78,14 +92,14 @@
@Test
fun `location based view models receive same icon id when common impl updates`() =
testScope.runTest {
- var latestHome: Int? = null
- val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
+ var latestHome: SignalIconModel? = null
+ val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
- var latestQs: Int? = null
- val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
+ var latestQs: SignalIconModel? = null
+ val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
- var latestKeyguard: Int? = null
- val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
+ var latestKeyguard: SignalIconModel? = null
+ val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
var expected = defaultSignal(level = 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b91a4df..bec276a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -19,16 +19,18 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
-import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +51,8 @@
class MobileIconViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor
- @Mock private lateinit var logger: ConnectivityPipelineLogger
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -59,6 +62,15 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ FakeConnectivityRepository(),
+ )
+
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
@@ -69,15 +81,94 @@
setNumberOfLevels(4)
isDataConnected.value = true
}
- underTest =
- MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+ createAndSetViewModel()
}
@Test
+ fun isVisible_notDataCapable_alwaysFalse() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.hasDataCapabilities).thenReturn(false)
+ createAndSetViewModel()
+
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_notAirplane_notForceHidden_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ interactor.isForceHidden.value = false
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_airplane_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ interactor.isForceHidden.value = false
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_forceHidden_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ interactor.isForceHidden.value = true
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_respondsToUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ interactor.isForceHidden.value = false
+
+ assertThat(latest).isTrue()
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ assertThat(latest).isFalse()
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ assertThat(latest).isTrue()
+
+ interactor.isForceHidden.value = true
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun iconId_correctLevel_notCutout() =
testScope.runTest {
- var latest: Int? = null
- val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
val expected = defaultSignal()
assertThat(latest).isEqualTo(expected)
@@ -90,8 +181,8 @@
testScope.runTest {
interactor.setIsDefaultDataEnabled(false)
- var latest: Int? = null
- val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
val expected = defaultSignal(level = 1, connected = false)
assertThat(latest).isEqualTo(expected)
@@ -102,8 +193,8 @@
@Test
fun `icon - uses empty state - when not in service`() =
testScope.runTest {
- var latest: Int? = null
- val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
interactor.isInService.value = false
@@ -364,14 +455,7 @@
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(false)
- underTest =
- MobileIconViewModel(
- SUB_1_ID,
- interactor,
- logger,
- constants,
- testScope.backgroundScope,
- )
+ createAndSetViewModel()
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -403,14 +487,7 @@
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
- underTest =
- MobileIconViewModel(
- SUB_1_ID,
- interactor,
- logger,
- constants,
- testScope.backgroundScope,
- )
+ createAndSetViewModel()
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -459,6 +536,16 @@
containerJob.cancel()
}
+ private fun createAndSetViewModel() {
+ underTest = MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+ }
+
companion object {
private const val SUB_1_ID = 1
@@ -466,10 +553,11 @@
fun defaultSignal(
level: Int = 1,
connected: Boolean = true,
- ): Int {
- return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+ ): SignalIconModel {
+ return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
}
- fun emptySignal(): Int = SignalDrawable.getEmptyState(4)
+ fun emptySignal(): SignalIconModel =
+ SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 58b50c7..d9268a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -20,11 +20,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,6 +49,7 @@
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@@ -57,6 +61,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ )
+
val subscriptionIdsFlow =
interactor.filteredSubscriptions
.map { subs -> subs.map { it.subscriptionId } }
@@ -66,6 +76,7 @@
MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
+ airplaneModeInteractor,
logger,
constants,
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index e1668e8..d51c514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.stylus
+import android.app.ActivityManager
import android.app.Notification
import android.content.BroadcastReceiver
import android.content.Context
@@ -27,6 +28,7 @@
import android.view.InputDevice
import androidx.core.app.NotificationManagerCompat
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
@@ -54,15 +56,23 @@
@SmallTest
class StylusUsiPowerUiTest : SysuiTestCase() {
@Mock lateinit var notificationManager: NotificationManagerCompat
+
@Mock lateinit var inputManager: InputManager
+
@Mock lateinit var handler: Handler
+
@Mock lateinit var btStylusDevice: InputDevice
+
+ @Mock lateinit var uiEventLogger: UiEventLogger
+
@Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>
private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var contextSpy: Context
+ private val uid = ActivityManager.getCurrentUser()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -80,7 +90,8 @@
whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
- stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler)
+ stylusUsiPowerUi =
+ StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger)
broadcastReceiver = stylusUsiPowerUi.receiver
}
@@ -197,6 +208,19 @@
}
@Test
+ fun updateBatteryState_showsNotification_logsNotificationShown() {
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+
+ verify(uiEventLogger, times(1))
+ .logWithPosition(
+ StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN,
+ uid,
+ contextSpy.packageName,
+ 10
+ )
+ }
+
+ @Test
fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() {
val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
val activityIntentCaptor = argumentCaptor<Intent>()
@@ -219,4 +243,32 @@
verify(contextSpy, never()).startActivity(any())
}
+
+ @Test
+ fun broadcastReceiver_clicked_logsNotificationClicked() {
+ val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
+ broadcastReceiver.onReceive(contextSpy, intent)
+
+ verify(uiEventLogger, times(1))
+ .logWithPosition(
+ StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED,
+ uid,
+ contextSpy.packageName,
+ 100
+ )
+ }
+
+ @Test
+ fun broadcastReceiver_dismissed_logsNotificationDismissed() {
+ val intent = Intent(StylusUsiPowerUI.ACTION_DISMISSED_LOW_BATTERY)
+ broadcastReceiver.onReceive(contextSpy, intent)
+
+ verify(uiEventLogger, times(1))
+ .logWithPosition(
+ StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED,
+ uid,
+ contextSpy.packageName,
+ 100
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
index 756397a..74ed7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
@@ -42,13 +42,35 @@
pixelDensity = 2f,
color = Color.RED,
opacity = 30,
- shouldFillRipple = true,
+ baseRingFadeParams =
+ RippleShader.FadeParams(
+ fadeInStart = 0f,
+ fadeInEnd = 0.3f,
+ fadeOutStart = 0.5f,
+ fadeOutEnd = 1f
+ ),
+ sparkleRingFadeParams =
+ RippleShader.FadeParams(
+ fadeInStart = 0.1f,
+ fadeInEnd = 0.2f,
+ fadeOutStart = 0.7f,
+ fadeOutEnd = 0.9f
+ ),
+ centerFillFadeParams =
+ RippleShader.FadeParams(
+ fadeInStart = 0f,
+ fadeInEnd = 0.1f,
+ fadeOutStart = 0.2f,
+ fadeOutEnd = 0.3f
+ ),
sparkleStrength = 0.3f
)
val rippleAnimation = RippleAnimation(config)
with(rippleAnimation.rippleShader) {
- assertThat(rippleFill).isEqualTo(config.shouldFillRipple)
+ assertThat(baseRingFadeParams).isEqualTo(config.baseRingFadeParams)
+ assertThat(sparkleRingFadeParams).isEqualTo(config.sparkleRingFadeParams)
+ assertThat(centerFillFadeParams).isEqualTo(config.centerFillFadeParams)
assertThat(pixelDensity).isEqualTo(config.pixelDensity)
assertThat(color).isEqualTo(ColorUtils.setAlphaComponent(config.color, config.opacity))
assertThat(sparkleStrength).isEqualTo(config.sparkleStrength)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index f4226bc..3d75967 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -105,8 +107,13 @@
}
keyguardRepository = FakeKeyguardRepository()
+ val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
val keyguardInteractor =
- KeyguardInteractor(repository = keyguardRepository, commandQueue = commandQueue)
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags
+ )
// Needs to be run on the main thread
runBlocking(IMMEDIATE) {
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 7380ca4..3538d9b 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
@@ -119,8 +119,11 @@
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
@@ -141,6 +144,7 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
manager = manager,
applicationScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 46f38ed..8f0375f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -82,7 +82,6 @@
private val userRepository = FakeUserRepository()
private val keyguardRepository = FakeKeyguardRepository()
- private val featureFlags = FakeFeatureFlags()
private lateinit var guestUserInteractor: GuestUserInteractor
private lateinit var refreshUsersScheduler: RefreshUsersScheduler
@@ -233,6 +232,11 @@
}
private fun viewModel(): StatusBarUserChipViewModel {
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
return StatusBarUserChipViewModel(
context = context,
interactor =
@@ -244,9 +248,9 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
- featureFlags =
- FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) },
+ featureFlags = featureFlags,
manager = manager,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
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 1d57d0b..71a112c 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
@@ -134,6 +134,11 @@
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
underTest =
UserSwitcherViewModel.Factory(
userInteractor =
@@ -145,11 +150,9 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags
),
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- },
+ featureFlags = featureFlags,
manager = manager,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
new file mode 100644
index 0000000..5ef62c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.util.condition;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ConditionalCoreStartableTest extends SysuiTestCase {
+ public static class FakeConditionalCoreStartable extends ConditionalCoreStartable {
+ interface Callback {
+ void onStart();
+ void bootCompleted();
+ }
+
+ private final Callback mCallback;
+
+ public FakeConditionalCoreStartable(Monitor monitor, Set<Condition> conditions,
+ Callback callback) {
+ super(monitor, conditions);
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onStart() {
+ mCallback.onStart();
+ }
+
+ @Override
+ protected void bootCompleted() {
+ mCallback.bootCompleted();
+ }
+ }
+
+
+ final Set<Condition> mConditions = new HashSet<>();
+
+ @Mock
+ Condition mCondition;
+
+ @Mock
+ Monitor mMonitor;
+
+ @Mock
+ FakeConditionalCoreStartable.Callback mCallback;
+
+ @Mock
+ Monitor.Subscription.Token mSubscriptionToken;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mConditions.clear();
+ }
+
+ /**
+ * Verifies that {@link ConditionalCoreStartable#onStart()} is predicated on conditions being
+ * met.
+ */
+ @Test
+ public void testOnStartCallback() {
+ final CoreStartable coreStartable =
+ new FakeConditionalCoreStartable(mMonitor,
+ new HashSet<>(Arrays.asList(mCondition)),
+ mCallback);
+
+ when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken);
+ coreStartable.start();
+
+ final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass(
+ Monitor.Subscription.class);
+ verify(mMonitor).addSubscription(subscriptionCaptor.capture());
+
+ final Monitor.Subscription subscription = subscriptionCaptor.getValue();
+
+ assertThat(subscription.getConditions()).containsExactly(mCondition);
+
+ verify(mCallback, never()).onStart();
+
+ subscription.getCallback().onConditionsChanged(true);
+
+ verify(mCallback).onStart();
+ verify(mMonitor).removeSubscription(mSubscriptionToken);
+ }
+
+
+ /**
+ * Verifies that {@link ConditionalCoreStartable#bootCompleted()} ()} is predicated on
+ * conditions being met.
+ */
+ @Test
+ public void testBootCompleted() {
+ final CoreStartable coreStartable =
+ new FakeConditionalCoreStartable(mMonitor,
+ new HashSet<>(Arrays.asList(mCondition)),
+ mCallback);
+
+ when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken);
+ coreStartable.onBootCompleted();
+
+ final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass(
+ Monitor.Subscription.class);
+ verify(mMonitor).addSubscription(subscriptionCaptor.capture());
+
+ final Monitor.Subscription subscription = subscriptionCaptor.getValue();
+
+ assertThat(subscription.getConditions()).containsExactly(mCondition);
+
+ verify(mCallback, never()).bootCompleted();
+
+ subscription.getCallback().onConditionsChanged(true);
+
+ verify(mCallback).bootCompleted();
+ verify(mMonitor).removeSubscription(mSubscriptionToken);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index c87d1c8..8e7d277 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -229,17 +229,12 @@
}
void onDisplayAdded(@NonNull Display display) {
- if (mInstalled) {
- resetStreamStateForDisplay(display.getDisplayId());
- enableFeaturesForDisplay(display);
- }
+ enableFeaturesForDisplayIfInstalled(display);
+
}
void onDisplayRemoved(int displayId) {
- if (mInstalled) {
- disableFeaturesForDisplay(displayId);
- resetStreamStateForDisplay(displayId);
- }
+ disableFeaturesForDisplayIfInstalled(displayId);
}
@Override
@@ -479,6 +474,9 @@
final Context displayContext = mContext.createDisplayContext(display);
final int displayId = display.getDisplayId();
+ if (mAms.isDisplayProxyed(displayId)) {
+ return;
+ }
if (!mServiceDetectsGestures.contains(displayId)) {
mServiceDetectsGestures.put(displayId, false);
}
@@ -613,6 +611,18 @@
mEventHandler.remove(displayId);
}
}
+ void enableFeaturesForDisplayIfInstalled(Display display) {
+ if (mInstalled) {
+ resetStreamStateForDisplay(display.getDisplayId());
+ enableFeaturesForDisplay(display);
+ }
+ }
+ void disableFeaturesForDisplayIfInstalled(int displayId) {
+ if (mInstalled) {
+ disableFeaturesForDisplay(displayId);
+ resetStreamStateForDisplay(displayId);
+ }
+ }
private void disableDisplayIndependentFeatures() {
if (mAutoclickController != null) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 776405d..d3593f0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -474,7 +474,7 @@
new MagnificationScaleProvider(mContext));
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
- mProxyManager = new ProxyManager(mLock, mA11yWindowManager);
+ mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext);
mFlashNotificationsController = new FlashNotificationsController(mContext);
init();
}
@@ -2495,6 +2495,7 @@
}
inputFilter = mInputFilter;
setInputFilter = true;
+ mProxyManager.setAccessibilityInputFilter(mInputFilter);
}
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
mInputFilter.setCombinedGenericMotionEventSources(
@@ -2725,6 +2726,7 @@
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
somethingChanged |= readMagnificationCapabilitiesLocked(userState);
somethingChanged |= readMagnificationFollowTypingLocked(userState);
+ somethingChanged |= readAlwaysOnMagnificationLocked(userState);
somethingChanged |= readUiContrastLocked(userState);
return somethingChanged;
}
@@ -3884,6 +3886,10 @@
return mProxyManager.unregisterProxy(displayId);
}
+ boolean isDisplayProxyed(int displayId) {
+ return mProxyManager.isProxyed(displayId);
+ }
+
@Override public float getUiContrast() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
@@ -4378,6 +4384,10 @@
private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
+ // TODO: replace name with Settings Secure Key
+ private final Uri mAlwaysOnMagnificationUri = Settings.Secure.getUriFor(
+ "accessibility_magnification_always_on_enabled");
+
private final Uri mUiContrastUri = Settings.Secure.getUriFor(
CONTRAST_LEVEL);
@@ -4422,6 +4432,8 @@
contentResolver.registerContentObserver(
mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
+ mAlwaysOnMagnificationUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
mUiContrastUri, false, this, UserHandle.USER_ALL);
}
@@ -4492,6 +4504,8 @@
}
} else if (mMagnificationFollowTypingUri.equals(uri)) {
readMagnificationFollowTypingLocked(userState);
+ } else if (mAlwaysOnMagnificationUri.equals(uri)) {
+ readAlwaysOnMagnificationLocked(userState);
} else if (mUiContrastUri.equals(uri)) {
if (readUiContrastLocked(userState)) {
updateUiContrastLocked(userState);
@@ -4605,6 +4619,23 @@
return false;
}
+ boolean readAlwaysOnMagnificationLocked(AccessibilityUserState userState) {
+ // TODO: replace name const with Settings Secure Key
+ final boolean isSettingsAlwaysOnEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ "accessibility_magnification_always_on_enabled",
+ 0, userState.mUserId) == 1;
+ final boolean isAlwaysOnFeatureFlagEnabled = mMagnificationController
+ .isAlwaysOnMagnificationFeatureFlagEnabled();
+ final boolean isAlwaysOnEnabled = isAlwaysOnFeatureFlagEnabled && isSettingsAlwaysOnEnabled;
+ if (isAlwaysOnEnabled != userState.isAlwaysOnMagnificationEnabled()) {
+ userState.setAlwaysOnMagnificationEnabled(isAlwaysOnEnabled);
+ mMagnificationController.setAlwaysOnMagnificationEnabled(isAlwaysOnEnabled);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
mMainHandler.sendMessage(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 43730fc..1c9ce3c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -136,6 +136,8 @@
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
// Whether the following typing focus feature for magnification is enabled.
private boolean mMagnificationFollowTypingEnabled = true;
+ // Whether the always on magnification feature is enabled.
+ private boolean mAlwaysOnMagnificationEnabled = false;
/** The stroke width of the focus rectangle in pixels */
private int mFocusStrokeWidth;
@@ -221,6 +223,7 @@
mFocusStrokeWidth = mFocusStrokeWidthDefaultValue;
mFocusColor = mFocusColorDefaultValue;
mMagnificationFollowTypingEnabled = true;
+ mAlwaysOnMagnificationEnabled = false;
mUiContrast = CONTRAST_NOT_SET;
}
@@ -531,6 +534,8 @@
.append(String.valueOf(mIsAudioDescriptionByDefaultRequested));
pw.append(", magnificationFollowTypingEnabled=")
.append(String.valueOf(mMagnificationFollowTypingEnabled));
+ pw.append(", alwaysOnMagnificationEnabled=")
+ .append(String.valueOf(mAlwaysOnMagnificationEnabled));
pw.append("}");
pw.println();
pw.append(" shortcut key:{");
@@ -711,6 +716,14 @@
return mMagnificationFollowTypingEnabled;
}
+ public void setAlwaysOnMagnificationEnabled(boolean enabled) {
+ mAlwaysOnMagnificationEnabled = enabled;
+ }
+
+ public boolean isAlwaysOnMagnificationEnabled() {
+ return mAlwaysOnMagnificationEnabled;
+ }
+
/**
* Sets the magnification mode to the given display.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 54cdb04..2530338 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -19,10 +19,12 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
import android.content.Context;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
+import android.view.Display;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -47,6 +49,8 @@
private final Object mLock;
+ private final Context mContext;
+
// Used to determine if we should notify AccessibilityManager clients of updates.
// TODO(254545943): Separate this so each display id has its own state. Currently there is no
// way to identify from AccessibilityManager which proxy state should be returned.
@@ -57,9 +61,12 @@
private AccessibilityWindowManager mA11yWindowManager;
- ProxyManager(Object lock, AccessibilityWindowManager awm) {
+ private AccessibilityInputFilter mA11yInputFilter;
+
+ ProxyManager(Object lock, AccessibilityWindowManager awm, Context context) {
mLock = lock;
mA11yWindowManager = awm;
+ mContext = context;
}
/**
@@ -109,6 +116,9 @@
connection.mSystemSupport.onClientChangeLocked(true);
}
+ if (mA11yInputFilter != null) {
+ mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
+ }
connection.initializeServiceInterface(client);
}
@@ -120,14 +130,25 @@
}
private boolean clearConnection(int displayId) {
+ boolean removed = false;
synchronized (mLock) {
if (mProxyA11yServiceConnections.contains(displayId)) {
mProxyA11yServiceConnections.remove(displayId);
- return true;
+ removed = true;
}
}
- mA11yWindowManager.stopTrackingDisplayProxy(displayId);
- return false;
+ if (removed) {
+ mA11yWindowManager.stopTrackingDisplayProxy(displayId);
+ if (mA11yInputFilter != null) {
+ final DisplayManager displayManager = (DisplayManager)
+ mContext.getSystemService(Context.DISPLAY_SERVICE);
+ final Display proxyDisplay = displayManager.getDisplay(displayId);
+ if (proxyDisplay != null) {
+ mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
+ }
+ }
+ }
+ return removed;
}
/**
@@ -251,4 +272,8 @@
proxy.notifyClearAccessibilityNodeInfoCache();
}
}
+
+ void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+ mA11yInputFilter = filter;
+ }
}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 595cdec..d7c5b5d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -57,6 +57,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.wm.WindowManagerInternal;
@@ -99,7 +100,8 @@
private final Rect mTempRect = new Rect();
// Whether the following typing focus feature for magnification is enabled.
private boolean mMagnificationFollowTypingEnabled = true;
-
+ // Whether the always on magnification feature is enabled.
+ private boolean mAlwaysOnMagnificationEnabled = false;
private final DisplayManagerInternal mDisplayManagerInternal;
/**
@@ -291,18 +293,15 @@
@Override
public void onDisplaySizeChanged() {
- // Treat as context change and reset
- final Message m = PooledLambda.obtainMessage(
- FullScreenMagnificationController::resetIfNeeded,
- FullScreenMagnificationController.this, mDisplayId, true);
- mControllerCtx.getHandler().sendMessage(m);
+ // Treat as context change
+ onUserContextChanged();
}
@Override
public void onUserContextChanged() {
final Message m = PooledLambda.obtainMessage(
- FullScreenMagnificationController::resetIfNeeded,
- FullScreenMagnificationController.this, mDisplayId, true);
+ FullScreenMagnificationController::onUserContextChanged,
+ FullScreenMagnificationController.this, mDisplayId);
mControllerCtx.getHandler().sendMessage(m);
}
@@ -795,6 +794,33 @@
return mMagnificationFollowTypingEnabled;
}
+ void setAlwaysOnMagnificationEnabled(boolean enabled) {
+ mAlwaysOnMagnificationEnabled = enabled;
+ }
+
+ boolean isAlwaysOnMagnificationEnabled() {
+ return mAlwaysOnMagnificationEnabled;
+ }
+
+ /**
+ * if the magnifier with given displayId is activated:
+ * 1. if {@link #isAlwaysOnMagnificationEnabled()}, zoom the magnifier to 100%,
+ * 2. otherwise, reset the magnification.
+ *
+ * @param displayId The logical display id.
+ */
+ void onUserContextChanged(int displayId) {
+ synchronized (mLock) {
+ if (isAlwaysOnMagnificationEnabled()) {
+ setScaleAndCenter(displayId, 1.0f, Float.NaN, Float.NaN,
+ true,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ } else {
+ reset(displayId, true);
+ }
+ }
+ }
+
/**
* Remove the display magnification with given id.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 9fc9d57..9c84c04 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -445,12 +445,12 @@
*/
final class ViewportDraggingState implements State {
- /** Whether to disable zoom after dragging ends */
- @VisibleForTesting boolean mActivatedBeforeDrag;
- /** Whether to restore scale after dragging ends */
- private boolean mZoomedInTemporary;
- /** The cached scale for recovering after dragging ends */
- private float mScaleBeforeZoomedInTemporary;
+ /**
+ * The cached scale for recovering after dragging ends.
+ * If the scale >= 1.0, the magnifier needs to recover to scale.
+ * Otherwise, the magnifier should be disabled.
+ */
+ @VisibleForTesting float mScaleToRecoverAfterDraggingEnd = Float.NaN;
private boolean mLastMoveOutsideMagnifiedRegion;
@@ -460,8 +460,7 @@
final int action = event.getActionMasked();
switch (action) {
case ACTION_POINTER_DOWN: {
- clear();
- transitionTo(mPanningScalingState);
+ clearAndTransitToPanningScalingState();
}
break;
case ACTION_MOVE: {
@@ -484,14 +483,18 @@
case ACTION_UP:
case ACTION_CANCEL: {
- if (mActivatedBeforeDrag) {
- if (mZoomedInTemporary) {
- zoomToScale(mScaleBeforeZoomedInTemporary, event.getX(), event.getY());
- }
+ // If mScaleToRecoverAfterDraggingEnd >= 1.0, the dragging state is triggered
+ // by zoom in temporary, and the magnifier needs to recover to original scale
+ // after exiting dragging state.
+ // Otherwise, the magnifier should be disabled.
+ if (mScaleToRecoverAfterDraggingEnd >= 1.0f) {
+ zoomToScale(mScaleToRecoverAfterDraggingEnd, event.getX(),
+ event.getY());
} else {
zoomOff();
}
clear();
+ mScaleToRecoverAfterDraggingEnd = Float.NaN;
transitionTo(mDetectingState);
}
break;
@@ -504,27 +507,49 @@
}
}
- public void prepareForZoomInTemporary() {
- mViewportDraggingState.mActivatedBeforeDrag =
- mFullScreenMagnificationController.isActivated(mDisplayId);
+ private boolean isAlwaysOnMagnificationEnabled() {
+ return mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled();
+ }
- mViewportDraggingState.mZoomedInTemporary = true;
- mViewportDraggingState.mScaleBeforeZoomedInTemporary =
- mFullScreenMagnificationController.getScale(mDisplayId);
+ public void prepareForZoomInTemporary(boolean shortcutTriggered) {
+ boolean shouldRecoverAfterDraggingEnd;
+ if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
+ // For b/267210808, if always-on feature is not enabled, we keep the expected
+ // behavior. If users tap shortcut and then tap-and-hold to zoom in temporary,
+ // the magnifier should be disabled after release.
+ // If always-on feature is enabled, in the same scenario the magnifier would
+ // zoom to 1.0 and keep activated.
+ if (shortcutTriggered) {
+ shouldRecoverAfterDraggingEnd = isAlwaysOnMagnificationEnabled();
+ } else {
+ shouldRecoverAfterDraggingEnd = true;
+ }
+ } else {
+ shouldRecoverAfterDraggingEnd = false;
+ }
+
+ mScaleToRecoverAfterDraggingEnd = shouldRecoverAfterDraggingEnd
+ ? mFullScreenMagnificationController.getScale(mDisplayId) : Float.NaN;
+ }
+
+ private void clearAndTransitToPanningScalingState() {
+ final float scaleToRecovery = mScaleToRecoverAfterDraggingEnd;
+ clear();
+ mScaleToRecoverAfterDraggingEnd = scaleToRecovery;
+ transitionTo(mPanningScalingState);
}
@Override
public void clear() {
mLastMoveOutsideMagnifiedRegion = false;
- mZoomedInTemporary = false;
- mScaleBeforeZoomedInTemporary = 1.0f;
+ mScaleToRecoverAfterDraggingEnd = Float.NaN;
}
@Override
public String toString() {
return "ViewportDraggingState{"
- + "mActivatedBeforeDrag=" + mActivatedBeforeDrag
+ + "mScaleToRecoverAfterDraggingEnd=" + mScaleToRecoverAfterDraggingEnd
+ ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion
+ '}';
}
@@ -921,13 +946,14 @@
void transitionToViewportDraggingStateAndClear(MotionEvent down) {
if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()");
+ final boolean shortcutTriggered = mShortcutTriggered;
clear();
// Triple tap and hold also belongs to triple tap event.
final boolean enabled = !isActivated();
logMagnificationTripleTap(enabled);
- mViewportDraggingState.prepareForZoomInTemporary();
+ mViewportDraggingState.prepareForZoomInTemporary(shortcutTriggered);
zoomInTemporary(down.getX(), down.getY());
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 558c71b..a6e6bd7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -677,6 +677,19 @@
getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled);
}
+ /**
+ * Called when the always on magnification feature is switched.
+ *
+ * @param enabled Enable the always on magnification feature
+ */
+ public void setAlwaysOnMagnificationEnabled(boolean enabled) {
+ getFullScreenMagnificationController().setAlwaysOnMagnificationEnabled(enabled);
+ }
+
+ public boolean isAlwaysOnMagnificationFeatureFlagEnabled() {
+ return AlwaysOnMagnificationFeatureFlag.isAlwaysOnMagnificationEnabled();
+ }
+
private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
int displayId) {
return mMagnificationEndRunnableSparseArray.get(displayId);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 7df4899..e86d997 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1028,8 +1028,9 @@
intent.setComponent(provider.getInfoLocked(mContext).configure);
intent.setFlags(secureFlags);
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setIgnorePendingIntentCreatorForegroundState(true);
+ final ActivityOptions options =
+ ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
// All right, create the sender.
final long identity = Binder.clearCallingIdentity();
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index b68adab..756dcd2 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -361,7 +361,6 @@
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
- params.setTrustedOverlay();
show();
}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fc81675..eafd3e0 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -584,7 +584,7 @@
+ packageInfo.applicationInfo.sourceDir);
if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
String origPackageFilepath = getOriginalApexPreinstalledLocation(
- packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+ packageInfo.packageName);
pw.println("|--> Pre-installed package install location: "
+ origPackageFilepath);
@@ -1628,8 +1628,7 @@
}
@NonNull
- private String getOriginalApexPreinstalledLocation(String packageName,
- String currentInstalledLocation) {
+ private String getOriginalApexPreinstalledLocation(String packageName) {
try {
final String moduleName = apexPackageNameToModuleName(packageName);
IApexService apexService = IApexService.Stub.asInterface(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 6435869..0252492 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3380,8 +3380,9 @@
appInfo.manageSpaceActivityName);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setIgnorePendingIntentCreatorForegroundState(true);
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
PendingIntent activity = PendingIntent.getActivity(targetAppContext, requestCode,
intent,
@@ -3747,8 +3748,12 @@
// Return both read only and write only volumes. When includeSharedProfile is
// true, all the volumes of userIdSharingMedia should be returned when queried
// from the user it shares media with
+ // Public Volumes will be also be returned if visible to the
+ // userIdSharingMedia with.
match = vol.isVisibleForUser(userId)
|| (!vol.isVisible() && includeInvisible && vol.getPath() != null)
+ || (vol.getType() == VolumeInfo.TYPE_PUBLIC
+ && vol.isVisibleForUser(userIdSharingMedia))
|| (includeSharedProfile && vol.isVisibleForUser(userIdSharingMedia));
}
if (!match) continue;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bb73877..c759af5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13282,8 +13282,10 @@
// restored. This distinction is important for system-process packages that live in the
// system user's process but backup/restore data for non-system users.
// TODO (b/123688746): Handle all system-process packages with singleton check.
- final int instantiatedUserId =
- PLATFORM_PACKAGE_NAME.equals(packageName) ? UserHandle.USER_SYSTEM : targetUserId;
+ boolean useSystemUser = PLATFORM_PACKAGE_NAME.equals(packageName)
+ || getPackageManagerInternal().getSystemUiServiceComponent().getPackageName()
+ .equals(packageName);
+ final int instantiatedUserId = useSystemUser ? UserHandle.USER_SYSTEM : targetUserId;
IPackageManager pm = AppGlobals.getPackageManager();
ApplicationInfo app = null;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 7290f32..c07ef1d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -273,7 +273,7 @@
performReceiveLocked(oldRecord.resultToApp, oldRecord.resultTo,
oldRecord.intent,
Activity.RESULT_CANCELED, null, null,
- false, false, r.shareIdentity, oldRecord.userId,
+ false, false, oldRecord.shareIdentity, oldRecord.userId,
oldRecord.callingUid, r.callingUid, r.callerPackage,
SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b952ce0..f954420 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1064,7 +1064,7 @@
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
- if (r.shareIdentity) {
+ if (r.shareIdentity && app.uid != r.callingUid) {
mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
UserHandle.getAppId(app.uid), r.callingUid, true);
}
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
index 153403a..63575ba 100644
--- a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -171,8 +171,9 @@
// Add a ReceiverInfo for a registered receiver.
void schedule(@Nullable IIntentReceiver receiver, Intent intent,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int callingUid,
- String callingPackage, int processState, @Nullable BroadcastRecord r, int index) {
+ boolean sticky, boolean assumeDelivered, int sendingUser, int sendingUid,
+ @Nullable String sendingPackage, int processState, @Nullable BroadcastRecord r,
+ int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
@@ -185,8 +186,8 @@
ri.receiver = receiver;
ri.ordered = ordered;
ri.sticky = sticky;
- ri.sentFromUid = callingUid;
- ri.sentFromPackage = callingPackage;
+ ri.sendingUid = sendingUid;
+ ri.sendingPackage = sendingPackage;
mReceivers.add(ri);
mCookies.add(cookiePool.next().set(r, index));
@@ -195,7 +196,7 @@
void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
@Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
@Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
- int callingUid, @Nullable String callingPackage, int processState,
+ int sendingUid, @Nullable String sendingPackage, int processState,
@Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
@@ -209,8 +210,8 @@
ri.activityInfo = activityInfo;
ri.compatInfo = compatInfo;
ri.sync = sync;
- ri.sentFromUid = callingUid;
- ri.sentFromPackage = callingPackage;
+ ri.sendingUid = sendingUid;
+ ri.sendingPackage = sendingPackage;
mReceivers.add(ri);
mCookies.add(cookiePool.next().set(r, index));
}
@@ -223,21 +224,21 @@
ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
@Nullable Intent intent, int resultCode, @Nullable String data,
@Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int callingUid, String callingPackage, int processState) {
+ int sendingUser, int sendingUid, @Nullable String sendingPackage, int processState) {
reset();
schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
- sendingUser, callingUid, callingPackage, processState, null, 0);
+ sendingUser, sendingUid, sendingPackage, processState, null, 0);
return receivers();
}
ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
@Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
- boolean assumeDelivered, int sendingUser, int callingUid, String callingPackage,
- int processState) {
+ boolean assumeDelivered, int sendingUser, int sendingUid,
+ @Nullable String sendingPackage, int processState) {
reset();
schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
- sendingUser, callingUid, callingPackage, processState, null, 0);
+ sendingUser, sendingUid, sendingPackage, processState, null, 0);
return receivers();
}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 5cdcd42..ef15beb 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -38,6 +38,8 @@
per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com
+per-file CachedAppOptimizer.java = file:/PERFORMANCE_OWNERS
+
# Multiuser
per-file User* = file:/MULTIUSER_OWNERS
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index dcb02ea..82dd5c2 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -48,12 +48,12 @@
@Override
public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
int resultCode, String data, Bundle extras, boolean ordered, boolean assumeDelivered,
- int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
+ int sendingUser, int processState, int sendingUid, String sendingPackage) {
mHandler.post(() -> {
try {
mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras,
- ordered, assumeDelivered, sendingUser, processState, sentFromUid,
- sentFromPackage);
+ ordered, assumeDelivered, sendingUser, processState, sendingUid,
+ sendingPackage);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -63,12 +63,12 @@
@Override
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
String data, Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
+ int sendingUser, int processState, int sendingUid, String sendingPackage) {
mHandler.post(() -> {
try {
mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
- ordered, sticky, assumeDelivered, sendingUser, processState, sentFromUid,
- sentFromPackage);
+ ordered, sticky, assumeDelivered, sendingUser, processState, sendingUid,
+ sendingPackage);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -82,11 +82,11 @@
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
- r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
+ r.sendingUser, r.processState, r.sendingUid, r.sendingPackage);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
- r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
+ r.sendingUser, r.processState, r.sendingUid, r.sendingPackage);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index c8d43e4..ffa5d20 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -563,6 +563,14 @@
}
}
+ // Set the default subtitle if necessary.
+ if (promptInfo.isUseDefaultSubtitle()) {
+ if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_default_subtitle));
+ }
+ }
+
final long requestId = mRequestCounter.get();
mHandler.post(() -> handleAuthenticate(
token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index d7306b7..66fe019 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -294,8 +294,6 @@
new int[] {ACTIVITY_TYPE_DREAM});
mListener.onDreamStopped(dream.mToken);
- } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) {
- mListener.stopDozing(dream.mToken);
}
} finally {
@@ -343,7 +341,6 @@
*/
public interface Listener {
void onDreamStopped(Binder token);
- void stopDozing(Binder token);
}
private final class DreamRecord implements DeathRecipient, ServiceConnection {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index caa5036..7802b9d 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -500,12 +500,7 @@
}
synchronized (mLock) {
- if (mCurrentDream == null) {
- return;
- }
-
- final boolean sameDream = mCurrentDream.token == token;
- if ((sameDream && mCurrentDream.isDozing) || (!sameDream && !mCurrentDream.isDozing)) {
+ if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
mCurrentDream.isDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
@@ -671,6 +666,10 @@
Slog.i(TAG, "Entering dreamland.");
+ if (mCurrentDream != null && mCurrentDream.isDozing) {
+ stopDozingInternal(mCurrentDream.token);
+ }
+
mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
@@ -776,11 +775,6 @@
}
}
}
-
- @Override
- public void stopDozing(Binder token) {
- stopDozingInternal(token);
- }
};
private final ContentObserver mDozeEnabledObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 002585f..5cfe6de 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3123,12 +3123,7 @@
translateUserId(userId, UserHandle.USER_NULL, "runSetUserRestriction");
final IUserManager um = IUserManager.Stub.asInterface(
ServiceManager.getService(Context.USER_SERVICE));
- try {
- um.setUserRestriction(restriction, value, translatedUserId);
- } catch (IllegalArgumentException e) {
- getErrPrintWriter().println(e.getMessage());
- return 1;
- }
+ um.setUserRestriction(restriction, value, translatedUserId);
return 0;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ce7dc5b..372b580 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2810,8 +2810,9 @@
}
if (!userExists(userId)) {
- throw new IllegalArgumentException("Cannot set user restriction. "
- + "User with this id does not exist");
+ Slogf.w(LOG_TAG, "Cannot set user restriction %s. User with id %d does not exist",
+ key, userId);
+ return;
}
synchronized (mRestrictionsLock) {
// Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
index 165b009..eed2a78 100644
--- a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -121,6 +121,9 @@
pw.println(" If the display option is not set, it uses the user's context to check");
pw.println(" (so it emulates what apps would get from UserManager.isUserVisible())");
pw.println();
+ pw.println(" get-main-user ");
+ pw.println(" Displays main user id or message if there is no main user");
+ pw.println();
}
@Override
@@ -145,6 +148,8 @@
return runIsVisibleBackgroundUserOnDefaultDisplaySupported();
case "is-user-visible":
return runIsUserVisible();
+ case "get-main-user":
+ return runGetMainUserId();
default:
return handleDefaultCommands(cmd);
}
@@ -516,6 +521,17 @@
return 0;
}
+ private int runGetMainUserId() {
+ PrintWriter pw = getOutPrintWriter();
+ final int mainUserId = mService.getMainUserId();
+ if (mainUserId == UserHandle.USER_NULL) {
+ pw.println("Couldn't get main user.");
+ return 1;
+ }
+ pw.println("Main user id: " + mainUserId);
+ return 0;
+ }
+
/**
* Gets the {@link UserManager} associated with the context of the given user.
*/
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index bc90c89..add4a89 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -12084,11 +12084,16 @@
final SparseDoubleArray uidEstimatedConsumptionMah;
final long dataConsumedChargeUC;
if (consumedChargeUC > 0 && isMobileRadioEnergyConsumerSupportedLocked()) {
- // Crudely attribute power consumption. Added (totalRadioDurationMs / 2) to the
- // numerator for long rounding.
- final long phoneConsumedChargeUC =
- (consumedChargeUC * phoneOnDurationMs + totalRadioDurationMs / 2)
- / totalRadioDurationMs;
+ final long phoneConsumedChargeUC;
+ if (totalRadioDurationMs == 0) {
+ phoneConsumedChargeUC = 0;
+ } else {
+ // Crudely attribute power consumption. Added (totalRadioDurationMs / 2) to the
+ // numerator for long rounding.
+ phoneConsumedChargeUC =
+ (consumedChargeUC * phoneOnDurationMs + totalRadioDurationMs / 2)
+ / totalRadioDurationMs;
+ }
dataConsumedChargeUC = consumedChargeUC - phoneConsumedChargeUC;
mGlobalEnergyConsumerStats.updateStandardBucket(
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 3226260..aba8e5f 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -264,8 +264,10 @@
if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
} else {
// Smear unattributed active time and add it to the remaining power consumption.
- total.remainingPowerMah +=
- (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+ if (totalActiveDurationMs != 0) {
+ total.remainingPowerMah +=
+ (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+ }
// Calculate the inactive modem power consumption.
final BatteryStats.ControllerActivityCounter modemActivity =
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c289153..2b9364c2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3919,6 +3919,10 @@
}
}
+ boolean isFinishing() {
+ return finishing;
+ }
+
/**
* This method is to only be called from the client via binder when the activity is destroyed
* AND finished.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index a7883cb..5e066fa 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -605,11 +605,16 @@
final Task task = r.getTask();
mService.deferWindowLayout();
try {
- r.mTransitionController.requestStartTransition(transition,
- task, remoteTransition, null /* displayChange */);
- r.mTransitionController.collect(task);
- r.mTransitionController.setTransientLaunch(r,
- TaskDisplayArea.getRootTaskAbove(rootTask));
+ final TransitionController controller = r.mTransitionController;
+ if (controller.getTransitionPlayer() != null) {
+ controller.requestStartTransition(transition, task, remoteTransition,
+ null /* displayChange */);
+ controller.collect(task);
+ controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
+ } else {
+ // The transition player might be died when executing the queued transition.
+ transition.abort();
+ }
task.moveToFront("startExistingRecents");
task.mInResumeTopActivity = true;
task.resumeTopActivity(null /* prev */, options, true /* deferPause */);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0f1f51f..f34dc94 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2053,7 +2053,7 @@
// Stop any activities that are scheduled to do so but have been waiting for the transition
// animation to finish.
ArrayList<ActivityRecord> readyToStopActivities = null;
- for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
+ for (int i = 0; i < mStoppingActivities.size(); i++) {
final ActivityRecord s = mStoppingActivities.get(i);
final boolean animating = s.isInTransition();
ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
@@ -2074,6 +2074,7 @@
readyToStopActivities.add(s);
mStoppingActivities.remove(i);
+ i--;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9060456..ca3cfaf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3952,23 +3952,6 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- // TODO: This should probably be called any time a visual change is made to the hierarchy like
- // moving containers or resizing them. Need to investigate the best way to have it automatically
- // happen so we don't run into issues with programmers forgetting to do it.
- void layoutAndAssignWindowLayersIfNeeded() {
- mWmService.mWindowsChanged = true;
- setLayoutNeeded();
-
- if (!mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
- false /*updateInputWindows*/)) {
- assignWindowLayers(false /* setLayoutNeeded */);
- }
-
- mInputMonitor.setUpdateInputWindowsNeededLw();
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- mInputMonitor.updateInputWindowsLw(false /*force*/);
- }
-
/** Returns true if a leaked surface was destroyed */
boolean destroyLeakedSurfaces() {
// Used to indicate that a surface was leaked.
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 7071aa7..c081725 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -635,7 +635,8 @@
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
if (!mDisplayContent.mTransitionController.isCollecting()) {
- throw new IllegalStateException("Trying to rotate outside a transition");
+ // The remote may be too slow to response before transition timeout.
+ Slog.e(TAG, "Trying to continue rotation outside a transition");
}
mDisplayContent.mTransitionController.collect(mDisplayContent);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9681789..1aa0ec3 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -105,6 +105,7 @@
import java.io.PrintWriter;
import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
@@ -114,6 +115,9 @@
// TODO(b/263021211): Consider renaming to more generic CompatUIController.
final class LetterboxUiController {
+ private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
+ activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing();
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
private static final float UNDEFINED_ASPECT_RATIO = 0f;
@@ -1390,7 +1394,8 @@
return;
}
final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
- ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+ FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
+ mActivityRecord /* boundary */, false /* includeBoundary */,
true /* traverseTopToBottom */);
if (firstOpaqueActivityBeneath == null) {
// We skip letterboxing if the translucent activity doesn't have any opaque
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1e53cc3..986c8f4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5873,9 +5873,6 @@
}
positionChildAt(POSITION_TOP, child, true /* includingParents */);
-
- final DisplayContent displayContent = getDisplayContent();
- displayContent.layoutAndAssignWindowLayersIfNeeded();
}
void positionChildAtBottom(Task child) {
@@ -5896,7 +5893,6 @@
}
positionChildAt(POSITION_BOTTOM, child, includingParents);
- getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
@Override
@@ -5910,12 +5906,6 @@
// Non-root task position changed.
mRootWindowContainer.invalidateTaskLayers();
}
-
- final boolean isTop = getTopChild() == child;
- if (isTop) {
- final DisplayContent displayContent = getDisplayContent();
- displayContent.layoutAndAssignWindowLayersIfNeeded();
- }
}
void reparent(TaskDisplayArea newParent, boolean onTop) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 0457408..a0608db 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -405,8 +405,6 @@
child.updateTaskMovement(moveToTop, targetPosition);
- mDisplayContent.layoutAndAssignWindowLayersIfNeeded();
-
// The insert position may be adjusted to non-top when there is always-on-top root task.
// Since the original position is preferred to be top, the root task should have higher
// priority when we are looking for top focusable root task. The condition {@code
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e1a144a..71bb99c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -427,7 +427,10 @@
if (mState < STATE_COLLECTING) {
throw new IllegalStateException("Can't start Transition which isn't collecting.");
} else if (mState >= STATE_STARTED) {
- Slog.w(TAG, "Transition already started: " + mSyncId);
+ Slog.w(TAG, "Transition already started id=" + mSyncId + " state=" + mState);
+ // The transition may be aborted (STATE_ABORT) or timed out (STATE_PLAYING by
+ // SyncGroup#finishNow), so do not revert the state to STATE_STARTED.
+ return;
}
mState = STATE_STARTED;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7173980..eec9973 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -566,7 +566,6 @@
onDisplayChanged(dc);
prevDc.setLayoutNeeded();
}
- getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
// Send onParentChanged notification here is we disabled sending it in setParent for
// reparenting case.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5f49aeb..ad81982 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3844,8 +3844,9 @@
*
* If {@code com.android.internal.R.bool.config_perDisplayFocusEnabled} is set to true, then
* only the display represented by the {@code displayId} parameter will be requested to switch
- * the touch mode state. Otherwise all all displays will be requested to switch their touch mode
- * state (disregarding {@code displayId} parameter).
+ * the touch mode state. Otherwise all displays that do not maintain their own focus and touch
+ * mode will be requested to switch their touch mode state (disregarding {@code displayId}
+ * parameter).
*
* To be able to change touch mode state, the caller must either own the focused window, or must
* have the {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE} permission. Instrumented
@@ -3857,27 +3858,9 @@
*/
@Override // Binder call
public void setInTouchMode(boolean inTouch, int displayId) {
- boolean perDisplayFocusEnabled = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_perDisplayFocusEnabled);
- setInTouchMode(inTouch, displayId, perDisplayFocusEnabled);
- }
-
- /**
- * Sets the touch mode state on all displays (disregarding the value of
- * {@code com.android.internal.R.bool.config_perDisplayFocusEnabled}).
- *
- * @param inTouch the touch mode to set
- */
- @Override // Binder call
- public void setInTouchModeOnAllDisplays(boolean inTouch) {
- setInTouchMode(inTouch, /* any display id */ DEFAULT_DISPLAY,
- /* perDisplayFocusEnabled= */ false);
- }
-
- private void setInTouchMode(boolean inTouch, int displayId, boolean perDisplayFocusEnabled) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (perDisplayFocusEnabled && (displayContent == null
+ if (mPerDisplayFocusEnabled && (displayContent == null
|| displayContent.isInTouchMode() == inTouch)) {
return;
}
@@ -3888,15 +3871,12 @@
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- final boolean hasPermission =
- mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE)
- || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()",
- /* printlog= */ false);
+ final boolean hasPermission = hasTouchModePermission(pid);
final long token = Binder.clearCallingIdentity();
try {
- // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+ // If mPerDisplayFocusEnabled is set or the display maintains its own touch mode,
// then just update the display pointed by displayId
- if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
+ if (mPerDisplayFocusEnabled || displayHasOwnTouchMode) {
if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
displayContent.setInTouchMode(inTouch);
}
@@ -3920,6 +3900,41 @@
}
/**
+ * Sets the touch mode state forcibly on all displays (disregarding both the value of
+ * {@code com.android.internal.R.bool.config_perDisplayFocusEnabled} and whether the display
+ * maintains its own focus and touch mode).
+ *
+ * @param inTouch the touch mode to set
+ */
+ @Override // Binder call
+ public void setInTouchModeOnAllDisplays(boolean inTouch) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final boolean hasPermission = hasTouchModePermission(pid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ for (int i = 0; i < mRoot.mChildren.size(); ++i) {
+ DisplayContent dc = mRoot.mChildren.get(i);
+ if (dc.isInTouchMode() != inTouch
+ && mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
+ dc.mDisplayId)) {
+ dc.setInTouchMode(inTouch);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean hasTouchModePermission(int pid) {
+ return mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE)
+ || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()",
+ /* printlog= */ false);
+ }
+
+ /**
* Returns the touch mode state for the display id passed as argument.
*/
@Override // Binder call
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 296b013..3faf9e0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -52,7 +52,6 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
-import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
@@ -617,8 +616,8 @@
}
}
- if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
- mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED);
+ if (effects != 0) {
+ mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
} finally {
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bf6bab3..e429db9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5665,14 +5665,6 @@
&& imeTarget.compareTo(this) <= 0;
return inTokenWithAndAboveImeTarget;
}
-
- // The condition is for the system dialog not belonging to any Activity.
- // (^FLAG_NOT_FOCUSABLE & FLAG_ALT_FOCUSABLE_IM) means the dialog is still focusable but
- // should be placed above the IME window.
- if ((mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM))
- == FLAG_ALT_FOCUSABLE_IM && isTrustedOverlay() && canAddInternalSystemWindow()) {
- return true;
- }
return false;
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 669fdb5..6857239 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -341,11 +341,12 @@
IGetCredentialCallback callback,
final String callingPackage) {
Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
- // TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
- int userId = UserHandle.getCallingUserId();
- int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
+ final int callingUid = Binder.getCallingUid();
+ enforceCallingPackage(callingPackage, callingUid);
+
// New request session, scoped for this request only.
final GetRequestSession session =
new GetRequestSession(
@@ -446,13 +447,14 @@
CreateCredentialRequest request,
ICreateCredentialCallback callback,
String callingPackage) {
- Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
-
+ Log.i(TAG, "starting executeCreateCredential with callingPackage: "
+ + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ final int userId = UserHandle.getCallingUserId();
+ final int callingUid = Binder.getCallingUid();
+ enforceCallingPackage(callingPackage, callingUid);
// New request session, scoped for this request only.
- int userId = UserHandle.getCallingUserId();
- int callingUid = Binder.getCallingUid();
final CreateRequestSession session =
new CreateRequestSession(
getContext(),
@@ -581,6 +583,8 @@
// TODO(253157366): Check additional set of services.
final int userId = UserHandle.getCallingUserId();
+ final int callingUid = Binder.getCallingUid();
+ enforceCallingPackage(callingPackage, callingUid);
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getServiceListForUserLocked(userId);
@@ -611,12 +615,14 @@
IClearCredentialStateCallback callback,
String callingPackage) {
Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
+ final int userId = UserHandle.getCallingUserId();
+ int callingUid = Binder.getCallingUid();
+ enforceCallingPackage(callingPackage, callingUid);
+
// TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// New request session, scoped for this request only.
- int userId = UserHandle.getCallingUserId();
- int callingUid = Binder.getCallingUid();
final ClearRequestSession session =
new ClearRequestSession(
getContext(),
@@ -655,6 +661,8 @@
throws IllegalArgumentException, NonCredentialProviderCallerException {
Log.i(TAG, "registerCredentialDescription");
+ enforceCallingPackage(callingPackage, Binder.getCallingUid());
+
List<CredentialProviderInfo> services =
CredentialProviderInfo.getAvailableServices(
mContext, UserHandle.getCallingUserId());
@@ -705,7 +713,8 @@
UnregisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException {
Log.i(TAG, "registerCredentialDescription");
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ enforceCallingPackage(callingPackage, Binder.getCallingUid());
List<CredentialProviderInfo> services =
CredentialProviderInfo.getAvailableServices(
@@ -728,4 +737,19 @@
session.executeUnregisterRequest(request, callingPackage);
}
}
+
+ private void enforceCallingPackage(String callingPackage, int callingUid) {
+ int packageUid;
+ PackageManager pm = mContext.createContextAsUser(
+ UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+ try {
+ packageUid = pm.getPackageUid(callingPackage,
+ PackageManager.PackageInfoFlags.of(0));
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new SecurityException(callingPackage + " not found");
+ }
+ if (packageUid != callingUid) {
+ throw new SecurityException(callingPackage + " does not belong to uid " + callingUid);
+ }
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index ec983df..20e358c 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -35,6 +35,7 @@
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import java.util.ArrayList;
@@ -62,6 +63,8 @@
private CreateCredentialException mProviderException;
+ @Nullable protected Pair<String, CreateEntry> mUiRemoteEntry;
+
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderCreateSession createNewSession(
Context context,
@@ -185,12 +188,22 @@
Log.i(TAG, "In prepareUiData save entries not null");
return prepareUiProviderData(
prepareUiSaveEntries(response.getCreateEntries()),
- null,
- /*isDefaultProvider=*/false);
+ prepareUiRemoteEntry(response.getRemoteCreateEntry()));
}
return null;
}
+ private Entry prepareUiRemoteEntry(CreateEntry remoteCreateEntry) {
+ if (remoteCreateEntry == null) {
+ return null;
+ }
+ String entryId = generateUniqueId();
+ Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCreateEntry.getSlice(),
+ setUpFillInIntent());
+ mUiRemoteEntry = new Pair<>(entryId, remoteCreateEntry);
+ return remoteEntry;
+ }
+
@Override
public void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
@@ -229,7 +242,7 @@
// Populate the save entries
for (CreateEntry createEntry : saveEntries) {
- String entryId = generateEntryId();
+ String entryId = generateUniqueId();
mUiSaveEntries.put(entryId, createEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, createEntry.getSlice(),
@@ -246,10 +259,11 @@
}
private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
- Entry remoteEntry, boolean isDefaultProvider) {
+ Entry remoteEntry) {
return new CreateCredentialProviderData.Builder(
mComponentName.flattenToString())
.setSaveEntries(saveEntries)
+ .setRemoteEntry(remoteEntry)
.build();
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 1583b83..9fba95b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -45,7 +45,6 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import java.util.stream.Collectors;
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
@@ -68,12 +67,16 @@
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
@NonNull
+ private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
+ @NonNull
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
private final Map<String, Action> mUiActionsEntries = new HashMap<>();
@Nullable
private final Map<String, Action> mUiAuthenticationEntries = new HashMap<>();
+ @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
+
/** The complete request to be used in the second round. */
private final android.credentials.GetCredentialRequest mCompleteRequest;
private final CallingAppInfo mCallingAppInfo;
@@ -91,12 +94,16 @@
filterOptions(providerInfo.getCapabilities(),
getRequestSession.mClientRequest);
if (filteredRequest != null) {
+ Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
+ new HashMap<>();
BeginGetCredentialRequest beginGetCredentialRequest = constructQueryPhaseRequest(
filteredRequest, getRequestSession.mClientAppInfo,
- getRequestSession.mClientRequest.alwaysSendAppInfoToProvider());
+ getRequestSession.mClientRequest.alwaysSendAppInfoToProvider(),
+ beginGetOptionToCredentialOptionMap);
return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
remoteCredentialService, beginGetCredentialRequest, filteredRequest,
- getRequestSession.mClientAppInfo);
+ getRequestSession.mClientAppInfo,
+ beginGetOptionToCredentialOptionMap);
}
Log.i(TAG, "Unable to create provider session");
return null;
@@ -105,15 +112,18 @@
private static BeginGetCredentialRequest constructQueryPhaseRequest(
android.credentials.GetCredentialRequest filteredRequest,
CallingAppInfo callingAppInfo,
- boolean propagateToProvider) {
+ boolean propagateToProvider,
+ Map<String, CredentialOption> beginGetOptionToCredentialOptionMap
+ ) {
BeginGetCredentialRequest.Builder builder = new BeginGetCredentialRequest.Builder();
- builder.setBeginGetCredentialOptions(
- filteredRequest.getCredentialOptions().stream().map(
- option -> {
- return new BeginGetCredentialOption(
- option.getType(),
- option.getCandidateQueryData());
- }).collect(Collectors.toList()));
+ filteredRequest.getCredentialOptions().forEach(option -> {
+ String id = generateUniqueId();
+ builder.addBeginGetCredentialOption(
+ new BeginGetCredentialOption(
+ id, option.getType(), option.getCandidateQueryData())
+ );
+ beginGetOptionToCredentialOptionMap.put(id, option);
+ });
if (propagateToProvider) {
builder.setCallingAppInfo(callingAppInfo);
}
@@ -152,11 +162,13 @@
int userId, RemoteCredentialService remoteCredentialService,
BeginGetCredentialRequest beginGetRequest,
android.credentials.GetCredentialRequest completeGetRequest,
- CallingAppInfo callingAppInfo) {
+ CallingAppInfo callingAppInfo,
+ Map<String, CredentialOption> beginGetOptionToCredentialOptionMap) {
super(context, info, beginGetRequest, callbacks, userId, remoteCredentialService);
mCompleteRequest = completeGetRequest;
mCallingAppInfo = callingAppInfo;
setStatus(Status.PENDING);
+ mBeginGetOptionToCredentialOptionMap = beginGetOptionToCredentialOptionMap;
}
/** Called when the provider response has been updated by an external source. */
@@ -260,8 +272,9 @@
if (remoteCredentialEntry == null) {
return null;
}
- String entryId = generateEntryId();
- Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice());
+ String entryId = generateUniqueId();
+ Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice(),
+ setUpFillInIntent(remoteCredentialEntry.getType()));
mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
return remoteEntry;
}
@@ -271,7 +284,7 @@
List<Entry> authenticationUiEntries = new ArrayList<>();
for (Action authenticationAction : authenticationEntries) {
- String entryId = generateEntryId();
+ String entryId = generateUniqueId();
mUiAuthenticationEntries.put(entryId, authenticationAction);
authenticationUiEntries.add(new Entry(
AUTHENTICATION_ACTION_ENTRY_KEY, entryId,
@@ -288,7 +301,7 @@
// Populate the credential entries
for (CredentialEntry credentialEntry : credentialEntries) {
- String entryId = generateEntryId();
+ String entryId = generateUniqueId();
mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
@@ -298,18 +311,17 @@
return credentialUiEntries;
}
- private Intent setUpFillInIntent(String type) {
- Intent intent = new Intent();
- for (CredentialOption option : mCompleteRequest.getCredentialOptions()) {
- if (option.getType().equals(type)) {
- intent.putExtra(
- CredentialProviderService
- .EXTRA_GET_CREDENTIAL_REQUEST,
- new GetCredentialRequest(mCallingAppInfo, option));
- return intent;
- }
+ private Intent setUpFillInIntent(@Nullable String id) {
+ // TODO: Determine if we should skip this entry if entry id is not set, or is set
+ // but does not resolve to a valid option. For now, not skipping it because
+ // it may be possible that the provider adds their own extras and expects to receive
+ // those and complete the flow.
+ if (id == null || mBeginGetOptionToCredentialOptionMap.get(id) == null) {
+ Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
}
- return intent;
+ return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
+ new GetCredentialRequest(
+ mCallingAppInfo, mBeginGetOptionToCredentialOptionMap.get(id)));
}
private Intent setUpFillInIntentForAuthentication() {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 461f447..dd9fcb6 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -109,7 +109,7 @@
// Populate the credential entries
for (CredentialEntry credentialEntry : credentialEntries) {
- String entryId = generateEntryId();
+ String entryId = generateUniqueId();
mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d6f97ff..1ae0f3c 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -25,10 +25,8 @@
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
-import android.util.Pair;
import java.util.UUID;
@@ -54,7 +52,7 @@
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
@NonNull protected Boolean mProviderResponseSet = false;
- @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
+
/**
@@ -145,7 +143,7 @@
return Status.CANCELED;
}
- protected String generateEntryId() {
+ protected static String generateUniqueId() {
return UUID.randomUUID().toString();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cb3b021..27371c0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -20,6 +20,7 @@
import static android.app.admin.PolicyUpdateResult.RESULT_SUCCESS;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_TARGET_USER_ID;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
+import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
import android.Manifest;
import android.annotation.NonNull;
@@ -32,6 +33,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -149,6 +151,8 @@
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
write();
+
+ applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
}
}
@@ -200,10 +204,52 @@
updateDeviceAdminServiceOnPolicyRemoveLocked(enforcingAdmin);
write();
+
+ applyToInheritableProfiles(policyDefinition, enforcingAdmin, /*value */ null, userId);
}
}
/**
+ * If any of child user has property {@link UserProperties#INHERIT_DEVICE_POLICY_FROM_PARENT}
+ * set then propagate the policy to it if value is not null
+ * else remove the policy from child.
+ */
+ private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
+ EnforcingAdmin enforcingAdmin, V value, int userId) {
+ if (policyDefinition.isInheritable()) {
+ Binder.withCleanCallingIdentity(() -> {
+ List<UserInfo> userInfos = mUserManager.getProfiles(userId);
+ for (UserInfo childUserInfo : userInfos) {
+ int childUserId = childUserInfo.getUserHandle().getIdentifier();
+ if (isProfileOfUser(childUserId, userId)
+ && isInheritDevicePolicyFromParent(childUserInfo)) {
+ if (value != null) {
+ setLocalPolicy(policyDefinition, enforcingAdmin, value, childUserId);
+ } else {
+ removeLocalPolicy(policyDefinition, enforcingAdmin, childUserId);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Checks if given parentUserId is direct parent of childUserId.
+ */
+ private boolean isProfileOfUser(int childUserId, int parentUserId) {
+ UserInfo parentInfo = mUserManager.getProfileParent(childUserId);
+ return childUserId != parentUserId && parentInfo != null
+ && parentInfo.getUserHandle().getIdentifier() == parentUserId;
+ }
+
+ private boolean isInheritDevicePolicyFromParent(UserInfo userInfo) {
+ UserProperties userProperties = mUserManager.getUserProperties(userInfo.getUserHandle());
+ return userProperties != null && mUserManager.getUserProperties(userInfo.getUserHandle())
+ .getInheritDevicePolicy() == INHERIT_DEVICE_POLICY_FROM_PARENT;
+ }
+
+ /**
* Enforces the new policy and notifies relevant admins.
*/
private <V> void onLocalPolicyChanged(
@@ -688,6 +734,47 @@
}
}
+ void handleUserCreated(UserInfo user) {
+ enforcePoliciesOnInheritableProfilesIfApplicable(user);
+ }
+
+ private void enforcePoliciesOnInheritableProfilesIfApplicable(UserInfo user) {
+ if (!user.isProfile()) {
+ return;
+ }
+
+ Binder.withCleanCallingIdentity(() -> {
+ UserProperties userProperties = mUserManager.getUserProperties(user.getUserHandle());
+ if (userProperties == null || userProperties.getInheritDevicePolicy()
+ != INHERIT_DEVICE_POLICY_FROM_PARENT) {
+ return;
+ }
+
+ int userId = user.id;
+ // Apply local policies present on parent to newly created child profile.
+ UserInfo parentInfo = mUserManager.getProfileParent(userId);
+ if (parentInfo == null || parentInfo.getUserHandle().getIdentifier() == userId) return;
+
+ for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
+ parentInfo.getUserHandle().getIdentifier()).entrySet()) {
+ enforcePolicyOnUser(userId, entry.getValue());
+ }
+ });
+ }
+
+ private <V> void enforcePolicyOnUser(int userId, PolicyState<V> policyState) {
+ if (!policyState.getPolicyDefinition().isInheritable()) {
+ return;
+ }
+ for (Map.Entry<EnforcingAdmin, V> enforcingAdminEntry :
+ policyState.getPoliciesSetByAdmins().entrySet()) {
+ setLocalPolicy(policyState.getPolicyDefinition(),
+ enforcingAdminEntry.getKey(),
+ enforcingAdminEntry.getValue(),
+ userId);
+ }
+ }
+
/**
* Handles internal state related to a user getting started.
*/
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5dafda4..fbde1ba 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -342,7 +342,6 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.stats.devicepolicy.DevicePolicyEnums;
-import android.telecom.TelecomManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
@@ -11339,6 +11338,11 @@
}
final int userId = user.id;
+
+ if (isCoexistenceFlagEnabled()) {
+ mDevicePolicyEngine.handleUserCreated(user);
+ }
+
if (token != null) {
synchronized (getLockObject()) {
if (mPendingUserCreatedCallbackTokens.contains(token)) {
@@ -20427,7 +20431,7 @@
final long id = mInjector.binderClearCallingIdentity();
try {
int parentUserId = getProfileParentId(caller.getUserId());
- installOemDefaultDialerAndMessagesApp(parentUserId, caller.getUserId());
+ installOemDefaultDialerAndSmsApp(caller.getUserId());
updateTelephonyCrossProfileIntentFilters(parentUserId, caller.getUserId(), true);
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -20435,32 +20439,31 @@
}
}
- private void installOemDefaultDialerAndMessagesApp(int sourceUserId, int targetUserId) {
+ private void installOemDefaultDialerAndSmsApp(int targetUserId) {
try {
- UserHandle sourceUserHandle = UserHandle.of(sourceUserId);
- TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
- String dialerAppPackage = telecomManager.getDefaultDialerPackage(
- sourceUserHandle);
- String messagesAppPackage = SmsApplication.getDefaultSmsApplicationAsUser(mContext,
- true, sourceUserHandle).getPackageName();
- if (dialerAppPackage != null) {
- mIPackageManager.installExistingPackageAsUser(dialerAppPackage, targetUserId,
- PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+ String defaultDialerPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultDialer);
+ String defaultSmsPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultSms);
+
+ if (defaultDialerPackageName != null) {
+ mIPackageManager.installExistingPackageAsUser(defaultDialerPackageName,
+ targetUserId, PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
PackageManager.INSTALL_REASON_POLICY, null);
} else {
Slogf.w(LOG_TAG, "Couldn't install dialer app, dialer app package is null");
}
- if (messagesAppPackage != null) {
- mIPackageManager.installExistingPackageAsUser(messagesAppPackage, targetUserId,
+ if (defaultSmsPackageName != null) {
+ mIPackageManager.installExistingPackageAsUser(defaultSmsPackageName, targetUserId,
PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
PackageManager.INSTALL_REASON_POLICY, null);
} else {
- Slogf.w(LOG_TAG, "Couldn't install messages app, messages app package is null");
+ Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null");
}
} catch (RemoteException re) {
// shouldn't happen
- Slogf.wtf(LOG_TAG, "Failed to install dialer/messages app", re);
+ Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index ab1658f..a7c4d5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -44,6 +44,9 @@
// Only use this flag if a policy can not be applied globally.
private static final int POLICY_FLAG_LOCAL_ONLY_POLICY = 1 << 1;
+ // Only use this flag if a policy is inheritable by child profile from parent.
+ private static final int POLICY_FLAG_INHERITABLE = 1 << 2;
+
private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(false, true));
@@ -210,6 +213,13 @@
return (mPolicyFlags & POLICY_FLAG_LOCAL_ONLY_POLICY) != 0;
}
+ /**
+ * Returns {@code true} if the policy is inheritable by child profiles.
+ */
+ boolean isInheritable() {
+ return (mPolicyFlags & POLICY_FLAG_INHERITABLE) != 0;
+ }
+
@Nullable
V resolvePolicy(LinkedHashMap<EnforcingAdmin, V> adminsPolicy) {
return mResolutionMechanism.resolve(adminsPolicy);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index db0a623..c293e09 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -216,4 +216,8 @@
}
return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
}
+
+ PolicyDefinition<V> getPolicyDefinition() {
+ return mPolicyDefinition;
+ }
}
diff --git a/services/tests/BackgroundInstallControlServiceTests/OWNERS b/services/tests/BackgroundInstallControlServiceTests/OWNERS
new file mode 100644
index 0000000..ca84550
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/transparency/OWNERS
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
new file mode 100644
index 0000000..4fcdbfc
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
@@ -0,0 +1,40 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_test_host {
+ name: "BackgroundInstallControlServiceHostTest",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tradefed",
+ "compatibility-tradefed",
+ "compatibility-host-util",
+ ],
+ data: [
+ ":BackgroundInstallControlServiceTestApp",
+ ":BackgroundInstallControlMockApp1",
+ ":BackgroundInstallControlMockApp2",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
new file mode 100644
index 0000000..1e7a78a
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Background Install Control Service integration test">
+ <option name="test-suite-tag" value="apct" />
+
+ <!-- Service is not exposed to apps. Disable SELinux for testing purpose. -->
+ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="BackgroundInstallControlServiceTestApp.apk" />
+ </target_preparer>
+
+ <!-- Load additional APKs onto device -->
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file"
+ key="BackgroundInstallControlMockApp1.apk"
+ value="/data/local/tmp/BackgroundInstallControlMockApp1.apk" />
+ <option name="push-file"
+ key="BackgroundInstallControlMockApp2.apk"
+ value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="BackgroundInstallControlServiceHostTest.jar" />
+ </test>
+</configuration>
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
new file mode 100644
index 0000000..7450607
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.pm.test;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// TODO: Add @Presubmit
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit4Test {
+ private static final String PACKAGE_NAME = "com.android.server.pm.test.app";
+
+ private static final String MOCK_PACKAGE_NAME_1 = "com.android.servicestests.apps.bicmockapp1";
+ private static final String MOCK_PACKAGE_NAME_2 = "com.android.servicestests.apps.bicmockapp2";
+
+ private static final String TEST_DATA_DIR = "/data/local/tmp/";
+
+ private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk";
+ private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk";
+
+ @Test
+ public void testGetMockBackgroundInstalledPackages() throws Exception {
+ installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1);
+ installPackage(TEST_DATA_DIR + MOCK_APK_FILE_2);
+
+ assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull();
+ assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull();
+
+ assertThat(getDevice().setProperty("debug.transparency.bg-install-apps",
+ MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue();
+ runDeviceTest("testGetMockBackgroundInstalledPackages");
+ assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull();
+ assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull();
+
+ assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNull();
+ assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNull();
+ }
+
+ private void installPackage(String path) throws DeviceNotAvailableException {
+ String cmd = "pm install -t --force-queryable " + path;
+ CommandResult result = getDevice().executeShellV2Command(cmd);
+ assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue();
+ }
+
+ private void runDeviceTest(String method) throws DeviceNotAvailableException {
+ var options = new DeviceTestRunOptions(PACKAGE_NAME);
+ options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest");
+ options.setTestMethodName(method);
+ runDeviceTests(options);
+ }
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/Android.bp
new file mode 100644
index 0000000..9b39f2d
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/Android.bp
@@ -0,0 +1,40 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "BackgroundInstallControlServiceTestApp",
+ manifest: "AndroidManifest.xml",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.core",
+ "compatibility-device-util-axt",
+ "junit",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+ platform_apis: true,
+ dex_preopt: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..1fa1f84
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.app">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="APCT tests for background install control service"
+ android:targetPackage="com.android.server.pm.test.app" />
+</manifest>
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
new file mode 100644
index 0000000..b74e561
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.pm.test.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundInstallControlServiceTest {
+ private static final String TAG = "BackgroundInstallControlServiceTest";
+
+ private IBackgroundInstallControlService mIBics;
+
+ @Before
+ public void setUp() {
+ mIBics = IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ assertThat(mIBics).isNotNull();
+ }
+
+ @Test
+ public void testGetMockBackgroundInstalledPackages() throws RemoteException {
+ ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages(
+ PackageManager.MATCH_ALL,
+ UserHandle.USER_ALL);
+ assertThat(slice).isNotNull();
+
+ var packageList = slice.getList();
+ assertThat(packageList).isNotNull();
+ assertThat(packageList).hasSize(2);
+
+ var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1",
+ "com.android.servicestests.apps.bicmockapp2");
+ var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName)
+ .collect(Collectors.toSet());
+ assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
+ }
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
new file mode 100644
index 0000000..7804f4c
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
@@ -0,0 +1,52 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_defaults {
+ name: "bic-mock-app-defaults",
+ test_suites: ["device-tests"],
+
+ srcs: ["**/*.java"],
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+}
+
+android_test_helper_app {
+ name: "BackgroundInstallControlMockApp1",
+ defaults: ["bic-mock-app-defaults"],
+ aaptflags: [
+ "--rename-manifest-package com.android.servicestests.apps.bicmockapp1",
+ ],
+}
+
+android_test_helper_app {
+ name: "BackgroundInstallControlMockApp2",
+ defaults: ["bic-mock-app-defaults"],
+ aaptflags: [
+ "--rename-manifest-package com.android.servicestests.apps.bicmockapp2",
+ ],
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/AndroidManifest.xml
new file mode 100644
index 0000000..3cb39db
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.bicmockapp">
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/res/values/strings.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/res/values/strings.xml
new file mode 100644
index 0000000..984152f
--- /dev/null
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this placeholder file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="placeholder">placeholder</string>
+</resources>
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 4a40b5f..9c581f9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -711,8 +711,6 @@
null); // description
// Case 8: App1 gets "remove task"
- final String app1Description = "remove task";
-
sleep(1);
final int app1IsolatedUidUser2 = 1099002; // isolated uid
final long app1Pss4 = 34343;
@@ -739,7 +737,7 @@
mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNKNOWN, app1Description, now8);
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, null, now8);
updateExitInfo(app, now8);
list.clear();
@@ -749,21 +747,21 @@
info = list.get(0);
verifyApplicationExitInfo(
- info, // info
- now8, // timestamp
- app1PidUser2, // pid
- app1IsolatedUidUser2, // uid
- app1UidUser2, // packageUid
- null, // definingUid
- app1ProcessName, // processName
- 0, // connectionGroup
- ApplicationExitInfo.REASON_OTHER, // reason
- ApplicationExitInfo.SUBREASON_UNKNOWN, // subReason
- 0, // status
- app1Pss4, // pss
- app1Rss4, // rss
- IMPORTANCE_CACHED, // importance
- app1Description); // description
+ info, // info
+ now8, // timestamp
+ app1PidUser2, // pid
+ app1IsolatedUidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_OTHER, // reason
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, // subReason
+ 0, // status
+ app1Pss4, // pss
+ app1Rss4, // rss
+ IMPORTANCE_CACHED, // importance
+ null); // description
// App1 gets "too many empty"
final String app1Description2 = "too many empty";
@@ -1058,7 +1056,18 @@
if (importance != null) {
assertEquals(importance.intValue(), info.getImportance());
}
- if (description != null) {
+
+ // info.getDescription returns a combination of subReason & description
+ if ((subReason != null) && (subReason != ApplicationExitInfo.SUBREASON_UNKNOWN)
+ && (description != null)) {
+ assertTrue(TextUtils.equals(
+ "[" + info.subreasonToString(subReason) + "] " + description,
+ info.getDescription()));
+ } else if ((subReason != null) && (subReason != ApplicationExitInfo.SUBREASON_UNKNOWN)) {
+ assertTrue(TextUtils.equals(
+ "[" + info.subreasonToString(subReason) + "]",
+ info.getDescription()));
+ } else if (description != null) {
assertTrue(TextUtils.equals(description, info.getDescription()));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 4d1d2b2..32b9864 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -34,6 +34,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -438,6 +439,42 @@
verify(mMockMagnificationController).setMagnificationFollowTypingEnabled(false);
}
+ @Test
+ public void testSettingsAlwaysOn_setEnabled_featureFlagDisabled_doNothing() {
+ when(mMockMagnificationController.isAlwaysOnMagnificationFeatureFlagEnabled())
+ .thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ Settings.Secure.putIntForUser(
+ mTestableContext.getContentResolver(),
+ // TODO: replace name with Settings Secure Key
+ "accessibility_magnification_always_on_enabled",
+ 1, mA11yms.getCurrentUserIdLocked());
+
+ mA11yms.readAlwaysOnMagnificationLocked(userState);
+
+ verify(mMockMagnificationController, never()).setAlwaysOnMagnificationEnabled(anyBoolean());
+ }
+
+ @Test
+ public void testSettingsAlwaysOn_setEnabled_featureFlagEnabled_propagateToController() {
+ when(mMockMagnificationController.isAlwaysOnMagnificationFeatureFlagEnabled())
+ .thenReturn(true);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ Settings.Secure.putIntForUser(
+ mTestableContext.getContentResolver(),
+ // TODO: replace name with Settings Secure Key
+ "accessibility_magnification_always_on_enabled",
+ 1, mA11yms.getCurrentUserIdLocked());
+
+ mA11yms.readAlwaysOnMagnificationLocked(userState);
+
+ verify(mMockMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
+ }
+
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index ed0336a..b4558b2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -179,6 +179,7 @@
assertEquals(mFocusStrokeWidthDefaultValue, mUserState.getFocusStrokeWidthLocked());
assertEquals(mFocusColorDefaultValue, mUserState.getFocusColorLocked());
assertTrue(mUserState.isMagnificationFollowTypingEnabled());
+ assertFalse(mUserState.isAlwaysOnMagnificationEnabled());
}
@Test
@@ -390,6 +391,15 @@
}
@Test
+ public void setAlwaysOnMagnificationEnabled_defaultFalseAndSetTrue_returnTrue() {
+ assertFalse(mUserState.isAlwaysOnMagnificationEnabled());
+
+ mUserState.setAlwaysOnMagnificationEnabled(true);
+
+ assertTrue(mUserState.isAlwaysOnMagnificationEnabled());
+ }
+
+ @Test
public void setFocusAppearanceData_returnExpectedFocusAppearanceData() {
final int focusStrokeWidthValue = 100;
final int focusColorValue = Color.BLUE;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index bf23d9d..2d036fe 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1196,6 +1196,27 @@
persistedScale);
}
+ @Test
+ public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
+ setScaleToMagnifying();
+
+ mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(false);
+ mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0);
+
+ verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false));
+ }
+
+ @Test
+ public void testOnContextChanged_alwaysOnFeatureEnabled_setScaleTo1xAndStayActivated() {
+ setScaleToMagnifying();
+
+ mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(true);
+ mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0);
+
+ assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0);
+ assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0));
+ }
+
private void setScaleToMagnifying() {
register(DISPLAY_0);
float scale = 2.0f;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 5334e4c..a095760 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -81,23 +81,27 @@
* IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
* IDLE -> DOUBLE_TAP [label="2tap"]
* DOUBLE_TAP -> IDLE [label="timeout"]
- * DOUBLE_TAP -> ZOOMED [label="tap"]
+ * DOUBLE_TAP -> ACTIVATED [label="tap"]
* DOUBLE_TAP -> NON_ACTIVATED_ZOOMED_TMP [label="hold"]
* NON_ACTIVATED_ZOOMED_TMP -> IDLE [label="release"]
* SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
- * SHORTCUT_TRIGGERED -> ZOOMED[label="tap"]
- * SHORTCUT_TRIGGERED -> ACTIVATED_ZOOMED_TMP [label="hold"]
+ * SHORTCUT_TRIGGERED -> ACTIVATED [label="tap"]
+ * SHORTCUT_TRIGGERED -> SHORTCUT_TRIGGERED_ZOOMED_TMP [label="hold"]
* SHORTCUT_TRIGGERED -> PANNING [label="2hold]
- * ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
- * ZOOMED -> IDLE [label="a11y\nbtn"]
- * ZOOMED -> PANNING [label="2hold"]
- * ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
- * ZOOMED_DOUBLE_TAP -> ACTIVATED_ZOOMED_TMP [label="hold"]
- * ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
- * ACTIVATED_ZOOMED_TMP -> ZOOMED [label="release"]
- * PANNING -> ZOOMED [label="release"]
+ * if always-on enabled:
+ * SHORTCUT_TRIGGERED_ZOOMED_TMP -> ACTIVATED [label="release"]
+ * else:
+ * SHORTCUT_TRIGGERED_ZOOMED_TMP -> IDLE [label="release"]
+ * ACTIVATED -> ACTIVATED_DOUBLE_TAP [label="2tap"]
+ * ACTIVATED -> IDLE [label="a11y\nbtn"]
+ * ACTIVATED -> PANNING [label="2hold"]
+ * ACTIVATED_DOUBLE_TAP -> ACTIVATED [label="timeout"]
+ * ACTIVATED_DOUBLE_TAP -> ACTIVATED_ZOOMED_TMP [label="hold"]
+ * ACTIVATED_DOUBLE_TAP -> IDLE [label="tap"]
+ * ACTIVATED_ZOOMED_TMP -> ACTIVATED [label="release"]
+ * PANNING -> ACTIVATED [label="release"]
* PANNING -> PANNING_SCALING [label="pinch"]
- * PANNING_SCALING -> ZOOMED [label="release"]
+ * PANNING_SCALING -> ACTIVATED [label="release"]
* }
* }
*/
@@ -105,14 +109,15 @@
public class FullScreenMagnificationGestureHandlerTest {
public static final int STATE_IDLE = 1;
- public static final int STATE_ZOOMED = 2;
+ public static final int STATE_ACTIVATED = 2;
public static final int STATE_2TAPS = 3;
- public static final int STATE_ZOOMED_2TAPS = 4;
+ public static final int STATE_ACTIVATED_2TAPS = 4;
public static final int STATE_SHORTCUT_TRIGGERED = 5;
public static final int STATE_NON_ACTIVATED_ZOOMED_TMP = 6;
public static final int STATE_ACTIVATED_ZOOMED_TMP = 7;
- public static final int STATE_PANNING = 8;
- public static final int STATE_SCALING_AND_PANNING = 9;
+ public static final int STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP = 8;
+ public static final int STATE_PANNING = 9;
+ public static final int STATE_SCALING_AND_PANNING = 10;
public static final int FIRST_STATE = STATE_IDLE;
public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
@@ -167,6 +172,7 @@
}
};
mFullScreenMagnificationController.register(DISPLAY_0);
+ mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(true);
mClock = new OffsettableClock.Stopped();
boolean detectTripleTap = true;
@@ -267,22 +273,22 @@
assertTransition(STATE_SHORTCUT_TRIGGERED, () -> {
send(downEvent());
fastForward1sec();
- }, STATE_ACTIVATED_ZOOMED_TMP);
+ }, STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP);
- // A11y button followed by a tap turns zoom on
- assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED);
+ // A11y button followed by a tap turns magnifier on
+ assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ACTIVATED);
// A11y button pressed second time negates the 1st press
assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE);
- // A11y button turns zoom off
- assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE);
+ // A11y button turns magnifier off
+ assertTransition(STATE_ACTIVATED, () -> triggerShortcut(), STATE_IDLE);
- // Double tap times out while zoomed
- assertTransition(STATE_ZOOMED_2TAPS, () -> {
+ // Double tap times out while activated
+ assertTransition(STATE_ACTIVATED_2TAPS, () -> {
allowEventDelegation();
fastForward1sec();
- }, STATE_ZOOMED);
+ }, STATE_ACTIVATED);
// tap+tap+swipe doesn't get delegated
assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
@@ -290,8 +296,29 @@
// tap+tap+swipe&hold initiates temporary viewport dragging zoom in immediately
assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_NON_ACTIVATED_ZOOMED_TMP);
- // release when activated temporary zoom in back to zoomed
- assertTransition(STATE_ACTIVATED_ZOOMED_TMP, () -> upEvent(), STATE_ZOOMED);
+ // release when activated temporary zoom in back to activated
+ assertTransition(STATE_ACTIVATED_ZOOMED_TMP, () -> send(upEvent()), STATE_ACTIVATED);
+ }
+
+ @Test
+ public void testRelease_shortcutTriggeredZoomedTmp_alwaysOnNotEnabled_shouldInIdle() {
+ mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(false);
+ goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP);
+ send(upEvent());
+
+ assertIn(STATE_IDLE);
+ }
+
+ @Test
+ public void testRelease_shortcutTriggeredZoomedTmp_alwaysOnEnabled_shouldInActivated() {
+ mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(true);
+ goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP);
+ send(upEvent());
+
+ assertIn(STATE_ACTIVATED);
+ assertTrue(!isZoomed());
+
+ returnToNormalFrom(STATE_ACTIVATED);
}
@Test
@@ -310,7 +337,7 @@
longTap();
};
assertStaysIn(STATE_IDLE, tapAndLongTap);
- assertStaysIn(STATE_ZOOMED, tapAndLongTap);
+ assertStaysIn(STATE_ACTIVATED, tapAndLongTap);
// Triple tap with delays in between doesn't count
Runnable slow3tap = () -> {
@@ -321,7 +348,7 @@
tap();
};
assertStaysIn(STATE_IDLE, slow3tap);
- assertStaysIn(STATE_ZOOMED, slow3tap);
+ assertStaysIn(STATE_ACTIVATED, slow3tap);
}
@Test
@@ -337,9 +364,9 @@
@Test
public void testTripleTapAndHold_zoomsImmediately() {
assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS, STATE_NON_ACTIVATED_ZOOMED_TMP);
- assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED, STATE_ACTIVATED_ZOOMED_TMP);
- assertZoomsImmediatelyOnSwipeFrom(STATE_ZOOMED_2TAPS, STATE_ACTIVATED_ZOOMED_TMP);
-
+ assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED,
+ STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP);
+ assertZoomsImmediatelyOnSwipeFrom(STATE_ACTIVATED_2TAPS, STATE_ACTIVATED_ZOOMED_TMP);
}
@Test
@@ -361,8 +388,8 @@
}
@Test
- public void testTwoFingersOneTap_zoomedState_dispatchMotionEvents() {
- goFromStateIdleTo(STATE_ZOOMED);
+ public void testTwoFingersOneTap_activatedState_dispatchMotionEvents() {
+ goFromStateIdleTo(STATE_ACTIVATED);
final EventCaptor eventCaptor = new EventCaptor();
mMgh.setNext(eventCaptor);
@@ -371,7 +398,7 @@
send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
send(upEvent());
- assertIn(STATE_ZOOMED);
+ assertIn(STATE_ACTIVATED);
final List<Integer> expectedActions = new ArrayList();
expectedActions.add(Integer.valueOf(ACTION_DOWN));
expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
@@ -379,12 +406,12 @@
expectedActions.add(Integer.valueOf(ACTION_UP));
assertActionsInOrder(eventCaptor.mEvents, expectedActions);
- returnToNormalFrom(STATE_ZOOMED);
+ returnToNormalFrom(STATE_ACTIVATED);
}
@Test
- public void testThreeFingersOneTap_zoomedState_dispatchMotionEvents() {
- goFromStateIdleTo(STATE_ZOOMED);
+ public void testThreeFingersOneTap_activatedState_dispatchMotionEvents() {
+ goFromStateIdleTo(STATE_ACTIVATED);
final EventCaptor eventCaptor = new EventCaptor();
mMgh.setNext(eventCaptor);
PointF pointer1 = DEFAULT_POINT;
@@ -398,7 +425,7 @@
send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}, 2));
send(upEvent());
- assertIn(STATE_ZOOMED);
+ assertIn(STATE_ACTIVATED);
final List<Integer> expectedActions = new ArrayList();
expectedActions.add(Integer.valueOf(ACTION_DOWN));
expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
@@ -408,12 +435,12 @@
expectedActions.add(Integer.valueOf(ACTION_UP));
assertActionsInOrder(eventCaptor.mEvents, expectedActions);
- returnToNormalFrom(STATE_ZOOMED);
+ returnToNormalFrom(STATE_ACTIVATED);
}
@Test
- public void testFirstFingerSwipe_twoPointerDownAndZoomedState_panningState() {
- goFromStateIdleTo(STATE_ZOOMED);
+ public void testFirstFingerSwipe_twoPointerDownAndActivatedState_panningState() {
+ goFromStateIdleTo(STATE_ACTIVATED);
PointF pointer1 = DEFAULT_POINT;
PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
@@ -429,8 +456,8 @@
}
@Test
- public void testSecondFingerSwipe_twoPointerDownAndZoomedState_panningState() {
- goFromStateIdleTo(STATE_ZOOMED);
+ public void testSecondFingerSwipe_twoPointerDownAndActivatedState_panningState() {
+ goFromStateIdleTo(STATE_ACTIVATED);
PointF pointer1 = DEFAULT_POINT;
PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
@@ -463,8 +490,8 @@
}
@Test
- public void testZoomedWithTripleTap_invokeShowWindowPromptAction() {
- goFromStateIdleTo(STATE_ZOOMED);
+ public void testActivatedWithTripleTap_invokeShowWindowPromptAction() {
+ goFromStateIdleTo(STATE_ACTIVATED);
verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
}
@@ -541,9 +568,8 @@
check(!isActivated(), state);
check(!isZoomed(), state);
} break;
- case STATE_ZOOMED: {
+ case STATE_ACTIVATED: {
check(isActivated(), state);
- check(isZoomed(), state);
check(tapCount() < 2, state);
} break;
case STATE_2TAPS: {
@@ -551,7 +577,7 @@
check(!isZoomed(), state);
check(tapCount() == 2, state);
} break;
- case STATE_ZOOMED_2TAPS: {
+ case STATE_ACTIVATED_2TAPS: {
check(isActivated(), state);
check(isZoomed(), state);
check(tapCount() == 2, state);
@@ -561,14 +587,29 @@
check(isZoomed(), state);
check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
state);
- check(!mMgh.mViewportDraggingState.mActivatedBeforeDrag, state);
+ check(Float.isNaN(mMgh.mViewportDraggingState.mScaleToRecoverAfterDraggingEnd),
+ state);
} break;
case STATE_ACTIVATED_ZOOMED_TMP: {
check(isActivated(), state);
check(isZoomed(), state);
check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
state);
- check(mMgh.mViewportDraggingState.mActivatedBeforeDrag, state);
+ check(mMgh.mViewportDraggingState.mScaleToRecoverAfterDraggingEnd >= 1.0f,
+ state);
+ } break;
+ case STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP: {
+ check(isActivated(), state);
+ check(isZoomed(), state);
+ check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
+ state);
+ if (mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled()) {
+ check(mMgh.mViewportDraggingState.mScaleToRecoverAfterDraggingEnd >= 1.0f,
+ state);
+ } else {
+ check(Float.isNaN(mMgh.mViewportDraggingState.mScaleToRecoverAfterDraggingEnd),
+ state);
+ }
} break;
case STATE_SHORTCUT_TRIGGERED: {
check(mMgh.mDetectingState.mShortcutTriggered, state);
@@ -605,7 +646,7 @@
tap();
tap();
} break;
- case STATE_ZOOMED: {
+ case STATE_ACTIVATED: {
if (mMgh.mDetectTripleTap) {
goFromStateIdleTo(STATE_2TAPS);
tap();
@@ -614,8 +655,8 @@
tap();
}
} break;
- case STATE_ZOOMED_2TAPS: {
- goFromStateIdleTo(STATE_ZOOMED);
+ case STATE_ACTIVATED_2TAPS: {
+ goFromStateIdleTo(STATE_ACTIVATED);
tap();
tap();
} break;
@@ -625,7 +666,12 @@
fastForward1sec();
} break;
case STATE_ACTIVATED_ZOOMED_TMP: {
- goFromStateIdleTo(STATE_ZOOMED_2TAPS);
+ goFromStateIdleTo(STATE_ACTIVATED_2TAPS);
+ send(downEvent());
+ fastForward1sec();
+ } break;
+ case STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP: {
+ goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
send(downEvent());
fastForward1sec();
} break;
@@ -634,7 +680,7 @@
triggerShortcut();
} break;
case STATE_PANNING: {
- goFromStateIdleTo(STATE_ZOOMED);
+ goFromStateIdleTo(STATE_ACTIVATED);
send(downEvent());
send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
fastForward(ViewConfiguration.getTapTimeout());
@@ -665,16 +711,16 @@
allowEventDelegation();
fastForward1sec();
} break;
- case STATE_ZOOMED: {
+ case STATE_ACTIVATED: {
if (mMgh.mDetectTripleTap) {
tap();
tap();
- returnToNormalFrom(STATE_ZOOMED_2TAPS);
+ returnToNormalFrom(STATE_ACTIVATED_2TAPS);
} else {
triggerShortcut();
}
} break;
- case STATE_ZOOMED_2TAPS: {
+ case STATE_ACTIVATED_2TAPS: {
tap();
} break;
case STATE_NON_ACTIVATED_ZOOMED_TMP: {
@@ -682,7 +728,13 @@
} break;
case STATE_ACTIVATED_ZOOMED_TMP: {
send(upEvent());
- returnToNormalFrom(STATE_ZOOMED);
+ returnToNormalFrom(STATE_ACTIVATED);
+ } break;
+ case STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP: {
+ send(upEvent());
+ if (mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled()) {
+ returnToNormalFrom(STATE_ACTIVATED);
+ }
} break;
case STATE_SHORTCUT_TRIGGERED: {
triggerShortcut();
@@ -690,7 +742,7 @@
case STATE_PANNING: {
send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
send(upEvent());
- returnToNormalFrom(STATE_ZOOMED);
+ returnToNormalFrom(STATE_ACTIVATED);
} break;
case STATE_SCALING_AND_PANNING: {
returnToNormalFrom(STATE_PANNING);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 407c575..b4a16c2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -739,6 +739,13 @@
}
@Test
+ public void setPreferenceAlwaysOnMagnificationEnabled_setPrefEnabled_enableOnFullScreen() {
+ mMagnificationController.setAlwaysOnMagnificationEnabled(true);
+
+ verify(mScreenMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
+ }
+
+ @Test
public void onRectangleOnScreenRequested_fullScreenIsActivated_fullScreenDispatchEvent() {
mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY,
true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
new file mode 100644
index 0000000..4434a32
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import static java.io.FileDescriptor.err;
+import static java.io.FileDescriptor.in;
+import static java.io.FileDescriptor.out;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * Test class for {@link UserManagerServiceShellCommand}.
+ *
+ * runtest atest UserManagerServiceShellCommandTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UserManagerServiceShellCommandTest {
+
+ private UserManagerServiceShellCommand mCommand;
+ private UserManagerService mUserManagerService;
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private final Binder mBinder = new Binder();
+ private final ShellCallback mShellCallback = new ShellCallback();
+ private final ResultReceiver mResultReceiver = new ResultReceiver(
+ new Handler(Looper.getMainLooper()));
+ private ByteArrayOutputStream mOutStream;
+ private PrintWriter mWriter;
+
+ @Before
+ public void setUp() throws Exception {
+ mOutStream = new ByteArrayOutputStream();
+ mWriter = new PrintWriter(new PrintStream(mOutStream));
+ MockitoAnnotations.initMocks(this);
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode();
+
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ final Context context = InstrumentationRegistry.getTargetContext();
+
+ UserManagerService serviceInstance = new UserManagerService(context);
+ mUserManagerService = spy(serviceInstance);
+
+ ArrayMap<String, UserTypeDetails> userTypes = UserTypeFactory.getUserTypes();
+ UserSystemPackageInstaller userSystemPackageInstaller =
+ new UserSystemPackageInstaller(mUserManagerService, userTypes);
+ UserManagerServiceShellCommand cmd = new UserManagerServiceShellCommand(mUserManagerService,
+ userSystemPackageInstaller, mLockPatternUtils, context);
+ mCommand = spy(cmd);
+ }
+
+ @Test
+ public void testMainUser() {
+ when(mUserManagerService.getMainUserId()).thenReturn(12);
+ doReturn(mWriter).when(mCommand).getOutPrintWriter();
+
+ assertEquals(0, mCommand.exec(mBinder, in, out, err,
+ new String[]{"get-main-user"}, mShellCallback, mResultReceiver));
+
+ mWriter.flush();
+ assertEquals("Main user id: 12", mOutStream.toString().trim());
+ }
+
+ @Test
+ public void testMainUserNull() {
+ when(mUserManagerService.getMainUserId()).thenReturn(UserHandle.USER_NULL);
+ doReturn(mWriter).when(mCommand).getOutPrintWriter();
+
+ assertEquals(1, mCommand.exec(mBinder, in, out, err,
+ new String[]{"get-main-user"}, mShellCallback, mResultReceiver));
+ mWriter.flush();
+ assertEquals("Couldn't get main user.", mOutStream.toString().trim());
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 00aa520..4af0323 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.os.UserManager.DISALLOW_BLUETOOTH;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static com.google.common.truth.Truth.assertThat;
@@ -41,7 +40,6 @@
import com.android.server.LocalServices;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -188,13 +186,11 @@
while (mUserManagerService.userExists(incorrectId)) {
incorrectId++;
}
- try {
- mUserManagerService.setUserRestriction(DISALLOW_BLUETOOTH, true, incorrectId);
- Assert.fail();
- } catch (IllegalArgumentException e) {
- //Exception is expected to be thrown if user ID does not exist.
- // IllegalArgumentException thrown means this test is successful.
- }
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, incorrectId))
+ .isFalse();
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, incorrectId);
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, incorrectId))
+ .isFalse();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 0568b38..c0a90b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -182,13 +182,11 @@
@Test
public void testApplicationNotInFocusWithModeId() {
final WindowState appWindow = createWindow("appWindow");
- assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
-
- final WindowState inFocusWindow = createWindow("inFocus");
- appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
-
+ mDisplayContent.mCurrentFocus = appWindow;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ mDisplayContent.mCurrentFocus = null;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
@@ -211,13 +209,11 @@
@Test
public void testApplicationNotInFocusWithoutModeId() {
final WindowState appWindow = createWindow("appWindow");
- assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
-
- final WindowState inFocusWindow = createWindow("inFocus");
- appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
-
+ mDisplayContent.mCurrentFocus = appWindow;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ mDisplayContent.mCurrentFocus = null;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fe7e04d..74f492f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -260,6 +260,22 @@
}
@Test
+ public void testCheckOpaqueIsLetterboxedWhenStrategyIsApplied() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ spyOn(mActivity);
+ mTask.addChild(translucentActivity);
+ verify(mActivity).isFinishing();
+ }
+
+ @Test
public void testRestartProcessIfVisible() {
setUpDisplaySizeWithApp(1000, 2500);
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 40e8273..060f9d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -352,6 +352,8 @@
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final ActivityRecord activity = createActivityRecord(mDisplayContent);
+ // Flush EVENT_APPEARED.
+ mController.dispatchPendingEvents();
final Task task = activity.getTask();
activity.info.applicationInfo.uid = uid;
doReturn(pid).when(activity).getPid();
@@ -379,6 +381,8 @@
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
activity.reparent(taskFragment, POSITION_TOP);
activity.mLastTaskFragmentOrganizerBeforePip = null;
+ // Flush EVENT_INFO_CHANGED.
+ mController.dispatchPendingEvents();
// Clear invocations now because there will be another transaction for the TaskFragment
// change above, triggered by the reparent. We only want to test onActivityReparentedToTask
@@ -400,6 +404,8 @@
final Task task = createTask(mDisplayContent);
task.addChild(mTaskFragment, POSITION_TOP);
final ActivityRecord activity = createActivityRecord(task);
+ // Flush EVENT_APPEARED.
+ mController.dispatchPendingEvents();
// Make sure the activity belongs to the same app, but it is in a different pid.
activity.info.applicationInfo.uid = uid;
@@ -442,6 +448,8 @@
final Task task = createTask(mDisplayContent);
task.addChild(mTaskFragment, POSITION_TOP);
final ActivityRecord activity = createActivityRecord(task);
+ // Flush EVENT_APPEARED.
+ mController.dispatchPendingEvents();
// Make sure the activity is embedded in untrusted mode.
activity.info.applicationInfo.uid = uid + 1;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 2984de9..4dd5de3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -500,6 +500,51 @@
virtualDisplay.getDisplay().getDisplayId());
}
+ @Test
+ public void testSetInTouchModeOnAllDisplays_perDisplayFocusDisabled() {
+ testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ false);
+ }
+
+ @Test
+ public void testSetInTouchModeOnAllDisplays_perDisplayFocusEnabled() {
+ testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ true);
+ }
+
+ private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) {
+ // Create a couple of extra displays.
+ // setInTouchModeOnAllDisplays should ignore the ownFocus setting.
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+ final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true);
+
+ // Enable or disable global touch mode (config_perDisplayFocusEnabled setting).
+ // setInTouchModeOnAllDisplays should ignore this value.
+ Resources mockResources = mock(Resources.class);
+ spyOn(mContext);
+ when(mContext.getResources()).thenReturn(mockResources);
+ doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean(
+ com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+ when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+ android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+ for (boolean inTouchMode : new boolean[]{true, false}) {
+ mWm.setInTouchModeOnAllDisplays(inTouchMode);
+ for (int i = 0; i < mWm.mRoot.mChildren.size(); ++i) {
+ DisplayContent dc = mWm.mRoot.mChildren.get(i);
+ // All displays that are not already in the desired touch mode are requested to
+ // change their touch mode.
+ if (dc.isInTouchMode() != inTouchMode) {
+ verify(mWm.mInputManager).setInTouchMode(
+ true, callingPid, callingUid, /* hasPermission= */ true,
+ dc.getDisplay().getDisplayId());
+ }
+ }
+ }
+ }
+
private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
// Create virtual display
Point surfaceSize = new Point(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index b2761d1..169586e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -871,6 +871,7 @@
lastReportedTiles.clear();
called[0] = false;
task1.positionChildAt(POSITION_TOP, rootTask, false /* includingParents */);
+ mAtm.mTaskOrganizerController.dispatchPendingEvents();
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
@@ -1124,11 +1125,11 @@
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
- final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
+ final ActivityRecord record = createActivityRecordAndDispatchPendingEvents(task);
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
- waitUntilHandlersIdle();
+ mAtm.mTaskOrganizerController.dispatchPendingEvents();
assertEquals("TestDescription", o.mChangedInfo.taskDescription.getLabel());
}
@@ -1288,6 +1289,8 @@
public void testAppearDeferThenInfoChange() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
+ // Flush EVENT_APPEARED.
+ mAtm.mTaskOrganizerController.dispatchPendingEvents();
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
@@ -1310,6 +1313,8 @@
public void testAppearDeferThenVanish() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
+ // Flush EVENT_APPEARED.
+ mAtm.mTaskOrganizerController.dispatchPendingEvents();
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
@@ -1328,7 +1333,7 @@
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
- final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
+ final ActivityRecord record = createActivityRecordAndDispatchPendingEvents(task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
@@ -1358,7 +1363,7 @@
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
- final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
+ final ActivityRecord record = createActivityRecordAndDispatchPendingEvents(task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
@@ -1381,7 +1386,7 @@
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
- final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
+ createActivityRecordAndDispatchPendingEvents(task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
@@ -1400,7 +1405,7 @@
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
- final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
+ final ActivityRecord record = createActivityRecordAndDispatchPendingEvents(task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
@@ -1465,13 +1470,12 @@
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
- final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task);
+ final ActivityRecord activity = createActivityRecordAndDispatchPendingEvents(task);
final ArgumentCaptor<RunningTaskInfo> infoCaptor =
ArgumentCaptor.forClass(RunningTaskInfo.class);
assertTrue(rootTask.isOrganized());
- spyOn(activity);
doReturn(true).when(activity).inSizeCompatMode();
doReturn(true).when(activity).isState(RESUMED);
@@ -1550,6 +1554,13 @@
verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
}
+ private ActivityRecord createActivityRecordAndDispatchPendingEvents(Task task) {
+ final ActivityRecord record = createActivityRecord(task);
+ // Flush EVENT_APPEARED.
+ mAtm.mTaskOrganizerController.dispatchPendingEvents();
+ return record;
+ }
+
/**
* Verifies that task vanished is called for a specific task.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 17b44ee..b2d8fed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -45,10 +45,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -103,7 +101,6 @@
import androidx.test.filters.SmallTest;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -124,15 +121,6 @@
@RunWith(WindowTestRunner.class)
public class WindowStateTests extends WindowTestsBase {
- @Before
- public void setUp() {
- // TODO: Let the insets source with new mode keep the visibility control, and remove this
- // setup code. Now mTopFullscreenOpaqueWindowState will take back the control of insets
- // visibility.
- spyOn(mDisplayContent);
- doNothing().when(mDisplayContent).layoutAndAssignWindowLayersIfNeeded();
- }
-
@Test
public void testIsParentWindowHidden() {
final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
@@ -988,19 +976,6 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
- @Test
- public void testNeedsRelativeLayeringToIme_systemDialog() {
- WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent,
- "SystemDialog", true);
- mDisplayContent.setImeLayeringTarget(mAppWindow);
- mAppWindow.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- makeWindowVisible(mImeWindow);
- systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
- assertTrue(systemDialogWindow.needsRelativeLayeringToIme());
- }
-
@Test
public void testSetFreezeInsetsState() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 9cde7e3..6cf2b2d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -22,7 +22,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -32,7 +31,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
@@ -552,30 +550,6 @@
}
@Test
- public void testSystemDialogWindow_expectHigherThanIme_inMultiWindow() {
- // Simulate the app window is in multi windowing mode and being IME target
- mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
- WINDOWING_MODE_MULTI_WINDOW);
- mDisplayContent.setImeLayeringTarget(mAppWindow);
- mDisplayContent.setImeInputTarget(mAppWindow);
- makeWindowVisible(mImeWindow);
-
- // Create a popupWindow
- final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent, "SystemDialog", true);
- systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
- spyOn(systemDialogWindow);
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- // Verify the surface layer of the popupWindow should higher than IME
- verify(systemDialogWindow).needsRelativeLayeringToIme();
- assertThat(systemDialogWindow.needsRelativeLayeringToIme()).isTrue();
- assertZOrderGreaterThan(mTransaction, systemDialogWindow.getSurfaceControl(),
- mDisplayContent.getImeContainer().getSurfaceControl());
- }
-
- @Test
public void testImeScreenshotLayer() {
final Task task = createTask(mDisplayContent);
final WindowState imeAppTarget = createAppWindow(task, TYPE_APPLICATION, "imeAppTarget");
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index acfc194..3bfbf03 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -6990,11 +6990,11 @@
/**
* Maximum Retry Count for SMS over IMS on Failure, If the Retry Count exceeds this value,
- * and if the retry count is less than KEY_SMS_MAX_RETRY_COUNT_INT
+ * and if the retry count is less than {@link #KEY_SMS_MAX_RETRY_COUNT_INT}
* sending SMS should fallback to CS
*/
- public static final String KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT =
- KEY_PREFIX + "sms_max_retry_count_over_ims_int";
+ public static final String KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT =
+ KEY_PREFIX + "sms_max_retry_over_ims_count_int";
/**
* Delay Timer Value in milliseconds
@@ -7062,7 +7062,7 @@
defaults.putInt(KEY_SMS_OVER_IMS_FORMAT_INT, SMS_FORMAT_3GPP);
defaults.putInt(KEY_SMS_MAX_RETRY_COUNT_INT, 3);
- defaults.putInt(KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT, 3);
+ defaults.putInt(KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 3);
defaults.putInt(KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT,
2000);
defaults.putInt(KEY_SMS_TR1_TIMER_MILLIS_INT, 130000);
@@ -8989,8 +8989,9 @@
"carrier_certificate_string_array";
/**
- * Flag specifying whether the incoming call number should be formatted to national number
- * for Japan. @return {@code true} convert to the national format, {@code false} otherwise.
+ * Flag specifying whether the incoming call number and the conference participant number
+ * should be formatted to national number for Japan.
+ * @return {@code true} convert to the national format, {@code false} otherwise.
* e.g. "+819012345678" -> "09012345678"
* @hide
*/
@@ -10059,7 +10060,7 @@
sDefaults.putAll(Bsf.getDefaults());
sDefaults.putAll(Iwlan.getDefaults());
sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]);
- sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
+ sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
new int[] {4 /* BUSY */});
sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java
index 5038aac..76394fe 100644
--- a/telephony/java/android/telephony/ims/MediaQualityStatus.java
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java
@@ -40,7 +40,7 @@
private final int mMediaSessionType;
private final int mTransportType;
private final int mRtpPacketLossRate;
- private final int mRtpJitter;
+ private final int mRtpJitterMillis;
private final long mRtpInactivityTimeMillis;
/** @hide */
@@ -52,23 +52,26 @@
public @interface MediaSessionType {}
/**
- * Constructor for this
+ * The constructor for MediaQualityStatus, which represents the media quality for each session
+ * type ({@link #MEDIA_SESSION_TYPE_AUDIO} or {@link #MEDIA_SESSION_TYPE_VIDEO}) of the IMS call
*
* @param imsCallSessionId IMS call session id of this quality status
* @param mediaSessionType media session type of this quality status
* @param transportType transport type of this quality status
- * @param rtpPacketLossRate measured RTP packet loss rate
- * @param rtpJitter measured RTP jitter value
+ * @param rtpPacketLossRate measured RTP packet loss rate in percentage
+ * @param rtpJitterMillis measured RTP jitter(RFC3550) in milliseconds
* @param rptInactivityTimeMillis measured RTP inactivity time in milliseconds
*/
- private MediaQualityStatus(@NonNull String imsCallSessionId,
+ public MediaQualityStatus(@NonNull String imsCallSessionId,
@MediaSessionType int mediaSessionType, @TransportType int transportType,
- int rtpPacketLossRate, int rtpJitter, long rptInactivityTimeMillis) {
+ @IntRange(from = 0, to = 100) int rtpPacketLossRate,
+ @IntRange(from = 0) int rtpJitterMillis,
+ @IntRange(from = 0) long rptInactivityTimeMillis) {
mImsCallSessionId = imsCallSessionId;
mMediaSessionType = mediaSessionType;
mTransportType = transportType;
mRtpPacketLossRate = rtpPacketLossRate;
- mRtpJitter = rtpJitter;
+ mRtpJitterMillis = rtpJitterMillis;
mRtpInactivityTimeMillis = rptInactivityTimeMillis;
}
@@ -106,13 +109,15 @@
/**
* Retrieves measured RTP jitter(RFC3550) value in milliseconds
*/
+ @IntRange(from = 0)
public int getRtpJitterMillis() {
- return mRtpJitter;
+ return mRtpJitterMillis;
}
/**
* Retrieves measured RTP inactivity time in milliseconds
*/
+ @IntRange(from = 0)
public long getRtpInactivityMillis() {
return mRtpInactivityTimeMillis;
}
@@ -126,7 +131,7 @@
mMediaSessionType = in.readInt();
mTransportType = in.readInt();
mRtpPacketLossRate = in.readInt();
- mRtpJitter = in.readInt();
+ mRtpJitterMillis = in.readInt();
mRtpInactivityTimeMillis = in.readLong();
}
@@ -136,7 +141,7 @@
dest.writeInt(mMediaSessionType);
dest.writeInt(mTransportType);
dest.writeInt(mRtpPacketLossRate);
- dest.writeInt(mRtpJitter);
+ dest.writeInt(mRtpJitterMillis);
dest.writeLong(mRtpInactivityTimeMillis);
}
@@ -167,14 +172,14 @@
&& mMediaSessionType == that.mMediaSessionType
&& mTransportType == that.mTransportType
&& mRtpPacketLossRate == that.mRtpPacketLossRate
- && mRtpJitter == that.mRtpJitter
+ && mRtpJitterMillis == that.mRtpJitterMillis
&& mRtpInactivityTimeMillis == that.mRtpInactivityTimeMillis;
}
@Override
public int hashCode() {
return Objects.hash(mImsCallSessionId, mMediaSessionType, mTransportType,
- mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis);
+ mRtpPacketLossRate, mRtpJitterMillis, mRtpInactivityTimeMillis);
}
@Override
@@ -188,100 +193,11 @@
sb.append(mTransportType);
sb.append(", mRtpPacketLossRate=");
sb.append(mRtpPacketLossRate);
- sb.append(", mRtpJitter=");
- sb.append(mRtpJitter);
+ sb.append(", mRtpJitterMillis=");
+ sb.append(mRtpJitterMillis);
sb.append(", mRtpInactivityTimeMillis=");
sb.append(mRtpInactivityTimeMillis);
sb.append("}");
return sb.toString();
}
-
- /**
- * Provides a convenient way to set the fields of an {@link MediaQualityStatus} when creating a
- * new instance.
- *
- * <p>The example below shows how you might create a new {@code RtpQualityStatus}:
- *
- * <pre><code>
- *
- * MediaQualityStatus = new MediaQualityStatus.Builder(
- * callSessionId, mediaSessionType, transportType)
- * .setRtpPacketLossRate(packetLossRate)
- * .setRtpJitter(jitter)
- * .setRtpInactivityMillis(inactivityTimeMillis)
- * .build();
- * </code></pre>
- */
- public static final class Builder {
- private final String mImsCallSessionId;
- private final int mMediaSessionType;
- private final int mTransportType;
- private int mRtpPacketLossRate;
- private int mRtpJitter;
- private long mRtpInactivityTimeMillis;
-
- /**
- * Default constructor for the Builder.
- */
- public Builder(
- @NonNull String imsCallSessionId,
- @MediaSessionType int mediaSessionType,
- @TransportType int transportType) {
- mImsCallSessionId = imsCallSessionId;
- mMediaSessionType = mediaSessionType;
- mTransportType = transportType;
- }
-
- /**
- * Set RTP packet loss info.
- *
- * @param packetLossRate RTP packet loss rate in percentage
- * @return The same instance of the builder.
- */
- @NonNull
- public Builder setRtpPacketLossRate(@IntRange(from = 0, to = 100) int packetLossRate) {
- this.mRtpPacketLossRate = packetLossRate;
- return this;
- }
-
- /**
- * Set calculated RTP jitter(RFC3550) value in milliseconds.
- *
- * @param jitter calculated RTP jitter value.
- * @return The same instance of the builder.
- */
- @NonNull
- public Builder setRtpJitterMillis(int jitter) {
- this.mRtpJitter = jitter;
- return this;
- }
-
- /**
- * Set measured RTP inactivity time.
- *
- * @param inactivityTimeMillis RTP inactivity time in Milliseconds.
- * @return The same instance of the builder.
- */
- @NonNull
- public Builder setRtpInactivityMillis(long inactivityTimeMillis) {
- this.mRtpInactivityTimeMillis = inactivityTimeMillis;
- return this;
- }
-
- /**
- * Build the {@link MediaQualityStatus}
- *
- * @return the {@link MediaQualityStatus} object
- */
- @NonNull
- public MediaQualityStatus build() {
- return new MediaQualityStatus(
- mImsCallSessionId,
- mMediaSessionType,
- mTransportType,
- mRtpPacketLossRate,
- mRtpJitter,
- mRtpInactivityTimeMillis);
- }
- }
}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
index 6d4ffcf..3567c08 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -92,7 +92,7 @@
super.onCreate();
IntentFilter filter = new IntentFilter();
filter.addAction(INTENT_ACTION);
- registerReceiver(mBroadcastReceiver, filter);
+ registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
// Make sure the data directory exists, and we're the owner of it.
try {
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index b43e4f7..9d9d24b 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -19,7 +19,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.ComponentName;
@@ -114,8 +113,9 @@
*
* Automatically binds to implementation of {@link SharedConnectivityService} specified in
* device overlay.
+ *
+ * @hide
*/
- @SuppressLint("ManagerConstructor")
public SharedConnectivityManager(@NonNull Context context) {
ServiceConnection serviceConnection = new ServiceConnection() {
@Override