Merge "Trigger an ANR for slow app responses."
diff --git a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
index a2aeb31..67b33e5 100644
--- a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
@@ -17,6 +17,7 @@
package android.view;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import android.content.Context;
import android.perftests.utils.PerfTestActivity;
@@ -72,6 +73,15 @@
@Test
public void testPerformHapticFeedback() throws Throwable {
+ // performHapticFeedback is now asynchronous, so should be very fast. This benchmark
+ // is primarily a regression test for the re-introduction of blocking calls in the path.
+
+ // Can't run back-to-back performHapticFeedback, as it will just enqueue on the oneway
+ // thread and fill up that buffer. Instead, we invoke at a speed of a fairly high frame
+ // rate - and this is still too fast to fully vibrate in reality, but should be able to
+ // clear queues.
+ int waitPerCallMillis = 5;
+
final BenchmarkState state = mBenchmarkRule.getState();
mActivityRule.runOnUiThread(() -> {
state.pauseTiming();
@@ -85,9 +95,17 @@
int flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
- while (state.keepRunning()) {
- assertTrue("Call to performHapticFeedback was ignored",
- view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS, flags));
+ try {
+ while (state.keepRunning()) {
+ assertTrue("Call to performHapticFeedback was ignored",
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS,
+ flags));
+ state.pauseTiming();
+ Thread.sleep(waitPerCallMillis);
+ state.resumeTiming();
+ }
+ } catch (InterruptedException e) {
+ fail("Unexpectedly interrupted");
}
});
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 1022490..bd8d27c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -27,6 +27,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.BackgroundStartPrivileges;
import android.app.UserSwitchObserver;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -388,6 +389,12 @@
private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
+ private final SparseIntArray mRecycledPrivilegedState = new SparseIntArray();
+
+ private static final int PRIVILEGED_STATE_UNDEFINED = 0;
+ private static final int PRIVILEGED_STATE_NONE = 1;
+ private static final int PRIVILEGED_STATE_BAL = 2;
+ private static final int PRIVILEGED_STATE_TOP = 3;
private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -792,7 +799,7 @@
cleanUpAfterAssignmentChangesLocked(
mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
- mRecycledAssignmentInfo);
+ mRecycledAssignmentInfo, mRecycledPrivilegedState);
noteConcurrency();
}
@@ -915,7 +922,8 @@
continue;
}
- final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending);
+ final boolean hasImmediacyPrivilege =
+ hasImmediacyPrivilegeLocked(nextPending, mRecycledPrivilegedState);
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
@@ -1183,7 +1191,8 @@
final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
final List<ContextAssignment> stoppable,
- final AssignmentInfo assignmentInfo) {
+ final AssignmentInfo assignmentInfo,
+ final SparseIntArray privilegedState) {
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
assignment.clear();
@@ -1205,20 +1214,58 @@
stoppable.clear();
preferredUidOnly.clear();
assignmentInfo.clear();
+ privilegedState.clear();
mWorkCountTracker.resetStagingCount();
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
}
@VisibleForTesting
@GuardedBy("mLock")
- boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) {
+ boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job,
+ @NonNull SparseIntArray cachedPrivilegedState) {
+ if (!job.shouldTreatAsExpeditedJob() && !job.shouldTreatAsUserInitiatedJob()) {
+ return false;
+ }
// EJs & user-initiated jobs for the TOP app should run immediately.
// However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
// state, we don't give the immediacy privilege so that we can try and maintain
// reasonably concurrency behavior.
- return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP
- // TODO(): include BAL state for user-initiated jobs
- && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob());
+ if (job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+ return true;
+ }
+ final int uid = job.getSourceUid();
+ final int privilegedState = cachedPrivilegedState.get(uid, PRIVILEGED_STATE_UNDEFINED);
+ switch (privilegedState) {
+ case PRIVILEGED_STATE_TOP:
+ return true;
+ case PRIVILEGED_STATE_BAL:
+ return job.shouldTreatAsUserInitiatedJob();
+ case PRIVILEGED_STATE_NONE:
+ return false;
+ case PRIVILEGED_STATE_UNDEFINED:
+ default:
+ final ActivityManagerInternal activityManagerInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
+ final int procState = activityManagerInternal.getUidProcessState(uid);
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ cachedPrivilegedState.put(uid, PRIVILEGED_STATE_TOP);
+ return true;
+ }
+ if (job.shouldTreatAsExpeditedJob()) {
+ // EJs only get the TOP privilege.
+ return false;
+ }
+
+ final BackgroundStartPrivileges bsp =
+ activityManagerInternal.getBackgroundStartPrivileges(uid);
+ final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
+ if (DEBUG) {
+ Slog.d(TAG, "Job " + job.toShortString() + " bal state: " + bsp);
+ }
+ cachedPrivilegedState.put(uid,
+ balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE);
+ return balAllowed;
+ }
}
@GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 419111a..22fea7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -31,6 +31,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.BackgroundStartPrivileges;
import android.app.IUidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
@@ -3841,6 +3842,25 @@
return callingResult;
}
}
+
+ final int uid = sourceUid != -1 ? sourceUid : callingUid;
+ final int procState = mActivityManagerInternal.getUidProcessState(uid);
+ if (DEBUG) {
+ Slog.d(TAG, "Uid " + uid + " proc state="
+ + ActivityManager.procStateToString(procState));
+ }
+ if (procState != ActivityManager.PROCESS_STATE_TOP) {
+ final BackgroundStartPrivileges bsp =
+ mActivityManagerInternal.getBackgroundStartPrivileges(uid);
+ if (DEBUG) {
+ Slog.d(TAG, "Uid " + uid + ": " + bsp);
+ }
+ if (!bsp.allowsBackgroundActivityStarts()) {
+ Slog.e(TAG,
+ "Uid " + uid + " not in a state to schedule user-initiated jobs");
+ return JobScheduler.RESULT_FAILURE;
+ }
+ }
}
if (jobWorkItem != null) {
jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
diff --git a/api/Android.bp b/api/Android.bp
index 318748e..f49e6dd 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -100,7 +100,7 @@
"framework-connectivity-t",
"framework-devicelock",
"framework-graphics",
- "framework-healthconnect",
+ "framework-healthfitness",
"framework-media",
"framework-mediaprovider",
"framework-ondevicepersonalization",
@@ -119,7 +119,7 @@
system_server_classpath: [
"service-art",
"service-configinfrastructure",
- "service-healthconnect",
+ "service-healthfitness",
"service-media-s",
"service-permission",
"service-rkp",
diff --git a/boot/Android.bp b/boot/Android.bp
index d4a6500..83a46c5 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -88,8 +88,8 @@
module: "com.android.devicelock-bootclasspath-fragment",
},
{
- apex: "com.android.healthconnect",
- module: "com.android.healthconnect-bootclasspath-fragment",
+ apex: "com.android.healthfitness",
+ module: "com.android.healthfitness-bootclasspath-fragment",
},
{
apex: "com.android.i18n",
diff --git a/core/api/current.txt b/core/api/current.txt
index 9e3e955..9274c0f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4319,6 +4319,7 @@
@UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
ctor public Activity();
method public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public void clearOverrideActivityTransition(int);
method public void closeContextMenu();
method public void closeOptionsMenu();
method public android.app.PendingIntent createPendingResult(int, @NonNull android.content.Intent, int);
@@ -4487,6 +4488,8 @@
method @Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
+ method public void overrideActivityTransition(int, @AnimRes int, @AnimRes int);
+ method public void overrideActivityTransition(int, @AnimRes int, @AnimRes int, @ColorInt int);
method public void overridePendingTransition(int, int);
method public void overridePendingTransition(int, int, int);
method public void postponeEnterTransition();
@@ -4588,6 +4591,8 @@
field protected static final int[] FOCUSED_STATE_SET;
field public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1; // 0x1
field public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0; // 0x0
+ field public static final int OVERRIDE_TRANSITION_CLOSE = 1; // 0x1
+ field public static final int OVERRIDE_TRANSITION_OPEN = 0; // 0x0
field public static final int RESULT_CANCELED = 0; // 0x0
field public static final int RESULT_FIRST_USER = 1; // 0x1
field public static final int RESULT_OK = -1; // 0xffffffff
@@ -4614,6 +4619,7 @@
method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
method @NonNull public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public java.util.List<android.app.ApplicationStartInfo> getHistoricalProcessStartReasons(@IntRange(from=0) int);
method public int getLargeMemoryClass();
method public int getLauncherLargeIconDensity();
method public int getLauncherLargeIconSize();
@@ -4639,7 +4645,9 @@
method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle);
+ method public void removeApplicationStartInfoCompleteListener();
method @Deprecated public void restartPackage(String);
+ method public void setApplicationStartInfoCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.ActivityManager.ApplicationStartInfoCompleteListener);
method public void setProcessStateSummary(@Nullable byte[]);
method public static void setVrThread(int);
method public void setWatchHeapLimit(long);
@@ -4662,6 +4670,10 @@
method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
}
+ public static interface ActivityManager.ApplicationStartInfoCompleteListener {
+ method public void onApplicationStartInfoComplete(@NonNull android.app.ApplicationStartInfo);
+ }
+
public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
ctor public ActivityManager.MemoryInfo();
method public int describeContents();
@@ -5221,6 +5233,52 @@
field public static final int REASON_USER_STOPPED = 11; // 0xb
}
+ public final class ApplicationStartInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getDefiningUid();
+ method @Nullable public android.content.Intent getIntent();
+ method public int getLaunchMode();
+ method public int getPackageUid();
+ method public int getPid();
+ method @NonNull public String getProcessName();
+ method public int getRealUid();
+ method public int getReason();
+ method public int getStartType();
+ method public int getStartupState();
+ method @NonNull public java.util.Map<java.lang.Integer,java.lang.Long> getStartupTimestamps();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationStartInfo> CREATOR;
+ field public static final int LAUNCH_MODE_SINGLE_INSTANCE = 2; // 0x2
+ field public static final int LAUNCH_MODE_SINGLE_INSTANCE_PER_TASK = 4; // 0x4
+ field public static final int LAUNCH_MODE_SINGLE_TASK = 3; // 0x3
+ field public static final int LAUNCH_MODE_SINGLE_TOP = 1; // 0x1
+ field public static final int LAUNCH_MODE_STANDARD = 0; // 0x0
+ field public static final int STARTUP_STATE_ERROR = 1; // 0x1
+ field public static final int STARTUP_STATE_FIRST_FRAME_DRAWN = 2; // 0x2
+ field public static final int STARTUP_STATE_STARTED = 0; // 0x0
+ field public static final int START_REASON_ALARM = 0; // 0x0
+ field public static final int START_REASON_BACKUP = 1; // 0x1
+ field public static final int START_REASON_BOOT_COMPLETE = 2; // 0x2
+ field public static final int START_REASON_BROADCAST = 3; // 0x3
+ field public static final int START_REASON_CONTENT_PROVIDER = 4; // 0x4
+ field public static final int START_REASON_JOB = 5; // 0x5
+ field public static final int START_REASON_LAUNCHER = 6; // 0x6
+ field public static final int START_REASON_OTHER = 7; // 0x7
+ field public static final int START_REASON_PUSH = 8; // 0x8
+ field public static final int START_REASON_RESUMED_ACTIVITY = 9; // 0x9
+ field public static final int START_REASON_SERVICE = 10; // 0xa
+ field public static final int START_REASON_START_ACTIVITY = 11; // 0xb
+ field public static final int START_TIMESTAMP_APPLICATION_ONCREATE = 2; // 0x2
+ field public static final int START_TIMESTAMP_BIND_APPLICATION = 3; // 0x3
+ field public static final int START_TIMESTAMP_FIRST_FRAME = 4; // 0x4
+ field public static final int START_TIMESTAMP_FULLY_DRAWN = 5; // 0x5
+ field public static final int START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE = 1; // 0x1
+ field public static final int START_TIMESTAMP_LAUNCH = 0; // 0x0
+ field public static final int START_TYPE_COLD = 0; // 0x0
+ field public static final int START_TYPE_HOT = 2; // 0x2
+ field public static final int START_TYPE_WARM = 1; // 0x1
+ }
+
public final class AsyncNotedAppOp implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getAttributionTag();
@@ -7697,6 +7755,26 @@
method public final android.os.IBinder onBind(android.content.Intent);
}
+ public final class DevicePolicyIdentifiers {
+ method @NonNull public static String getIdentifierForUserRestriction(@NonNull String);
+ field public static final String ACCOUNT_MANAGEMENT_DISABLED_POLICY = "accountManagementDisabled";
+ field public static final String APPLICATION_HIDDEN_POLICY = "applicationHidden";
+ field public static final String APPLICATION_RESTRICTIONS_POLICY = "applicationRestrictions";
+ field public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
+ field public static final String AUTO_TIME_POLICY = "autoTime";
+ field public static final String BACKUP_SERVICE_POLICY = "backupService";
+ field public static final String CAMERA_DISABLED_POLICY = "cameraDisabled";
+ field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures";
+ field public static final String LOCK_TASK_POLICY = "lockTask";
+ field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
+ field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
+ field public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
+ field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity";
+ field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
+ field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
+ field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages";
+ }
+
public class DevicePolicyManager {
method public void acknowledgeDeviceCompliant();
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
@@ -7704,6 +7782,7 @@
method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
method public void addPersistentPreferredActivity(@NonNull android.content.ComponentName, android.content.IntentFilter, @NonNull android.content.ComponentName);
method public void addUserRestriction(@NonNull android.content.ComponentName, String);
+ method public void addUserRestrictionGlobally(@NonNull String);
method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method public boolean canAdminGrantSensorsPermissions();
method public boolean canUsbDataSignalingBeDisabled();
@@ -7806,6 +7885,7 @@
method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getUserControlDisabledPackages(@NonNull android.content.ComponentName);
method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName);
+ method @NonNull public android.os.Bundle getUserRestrictionsGlobally();
method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName);
method @Nullable public android.app.admin.WifiSsidPolicy getWifiSsidPolicy();
method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
@@ -7848,6 +7928,7 @@
method public boolean isResetPasswordTokenActive(android.content.ComponentName);
method public boolean isSafeOperation(int);
method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
+ method public boolean isStatusBarDisabled();
method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
method public boolean isUniqueDeviceAttestationSupported();
method public boolean isUsbDataSignalingEnabled();
@@ -7882,7 +7963,7 @@
method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
- method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public void setCameraDisabled(@Nullable android.content.ComponentName, boolean);
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
@@ -8088,6 +8169,7 @@
field @Deprecated public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
+ field public static final int KEYGUARD_DISABLE_SHORTCUTS_ALL = 512; // 0x200
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
@@ -8249,6 +8331,8 @@
ctor public PolicyUpdateResult(int);
method public int getResultCode();
field public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1; // 0x1
+ field public static final int RESULT_FAILURE_HARDWARE_LIMITATION = 4; // 0x4
+ field public static final int RESULT_FAILURE_STORAGE_LIMIT_REACHED = 3; // 0x3
field public static final int RESULT_FAILURE_UNKNOWN = -1; // 0xffffffff
field public static final int RESULT_POLICY_CLEARED = 2; // 0x2
field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -8261,6 +8345,7 @@
method public final void onReceive(android.content.Context, android.content.Intent);
field public static final String ACTION_DEVICE_POLICY_CHANGED = "android.app.admin.action.DEVICE_POLICY_CHANGED";
field public static final String ACTION_DEVICE_POLICY_SET_RESULT = "android.app.admin.action.DEVICE_POLICY_SET_RESULT";
+ field public static final String EXTRA_ACCOUNT_TYPE = "android.app.admin.extra.ACCOUNT_TYPE";
field public static final String EXTRA_INTENT_FILTER = "android.app.admin.extra.INTENT_FILTER";
field public static final String EXTRA_PACKAGE_NAME = "android.app.admin.extra.PACKAGE_NAME";
field public static final String EXTRA_PERMISSION_NAME = "android.app.admin.extra.PERMISSION_NAME";
@@ -9461,10 +9546,10 @@
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
- method public void disableSystemDataSync(int, int);
+ method public void disableSystemDataSyncForTypes(int, int);
method @Deprecated public void disassociate(@NonNull String);
method public void disassociate(int);
- method public void enableSystemDataSync(int, int);
+ method public void enableSystemDataSyncForTypes(int, int);
method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
@@ -14850,6 +14935,7 @@
method @Nullable public android.graphics.ColorSpace getColorSpace();
method @NonNull public android.graphics.Bitmap.Config getConfig();
method public int getDensity();
+ method @Nullable public android.graphics.Gainmap getGainmap();
method public int getGenerationId();
method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
method public int getHeight();
@@ -14865,6 +14951,7 @@
method public int getScaledWidth(int);
method public int getWidth();
method public boolean hasAlpha();
+ method public boolean hasGainmap();
method public boolean hasMipMap();
method public boolean isMutable();
method public boolean isPremultiplied();
@@ -14876,6 +14963,7 @@
method public void setColorSpace(@NonNull android.graphics.ColorSpace);
method public void setConfig(@NonNull android.graphics.Bitmap.Config);
method public void setDensity(int);
+ method public void setGainmap(@Nullable android.graphics.Gainmap);
method public void setHasAlpha(boolean);
method public void setHasMipMap(boolean);
method public void setHeight(int);
@@ -15411,6 +15499,26 @@
ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
}
+ public final class Gainmap {
+ ctor public Gainmap(@NonNull android.graphics.Bitmap);
+ method @NonNull public float getDisplayRatioForFullHdr();
+ method @NonNull public float[] getEpsilonHdr();
+ method @NonNull public float[] getEpsilonSdr();
+ method @NonNull public android.graphics.Bitmap getGainmapContents();
+ method @NonNull public float[] getGamma();
+ method @NonNull public float getMinDisplayRatioForHdrTransition();
+ method @NonNull public float[] getRatioMax();
+ method @NonNull public float[] getRatioMin();
+ method @NonNull public void setDisplayRatioForFullHdr(float);
+ method @NonNull public void setEpsilonHdr(float, float, float);
+ method @NonNull public void setEpsilonSdr(float, float, float);
+ method public void setGainmapContents(@NonNull android.graphics.Bitmap);
+ method @NonNull public void setGamma(float, float, float);
+ method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
+ method @NonNull public void setRatioMax(float, float, float);
+ method @NonNull public void setRatioMin(float, float, float);
+ }
+
public class HardwareBufferRenderer implements java.lang.AutoCloseable {
ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer);
method public void close();
@@ -27162,6 +27270,10 @@
field public static final int SIGNAL_STRENGTH_STRONG = 3; // 0x3
field public static final int SIGNAL_STRENGTH_WEAK = 2; // 0x2
field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L
+ field public static final int TIME_SHIFT_MODE_AUTO = 4; // 0x4
+ field public static final int TIME_SHIFT_MODE_LOCAL = 2; // 0x2
+ field public static final int TIME_SHIFT_MODE_NETWORK = 3; // 0x3
+ field public static final int TIME_SHIFT_MODE_OFF = 1; // 0x1
field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
@@ -27246,11 +27358,14 @@
method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
method public void notifyAudioPresentationChanged(@NonNull java.util.List<android.media.AudioPresentation>);
method public void notifyAudioPresentationSelected(int, int);
+ method public void notifyAvailableSpeeds(@NonNull float[]);
method public void notifyBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
method public void notifyChannelRetuned(android.net.Uri);
method public void notifyContentAllowed();
method public void notifyContentBlocked(@NonNull android.media.tv.TvContentRating);
+ method public void notifyCueingMessageAvailability(boolean);
method public void notifySignalStrength(int);
+ method public void notifyTimeShiftMode(int);
method public void notifyTimeShiftStatusChanged(int);
method public void notifyTrackSelected(int, String);
method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>);
@@ -27285,6 +27400,7 @@
method public void onTimeShiftPlay(android.net.Uri);
method public void onTimeShiftResume();
method public void onTimeShiftSeekTo(long);
+ method public void onTimeShiftSetMode(int);
method public void onTimeShiftSetPlaybackParams(android.media.PlaybackParams);
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
@@ -27302,6 +27418,7 @@
method public void resumeRecording();
method public void resumeRecording(@NonNull android.os.Bundle);
method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
+ method public void setTvInteractiveAppView(@Nullable android.media.tv.interactive.TvInteractiveAppView, @Nullable String);
method public void startRecording(@Nullable android.net.Uri);
method public void startRecording(@Nullable android.net.Uri, @NonNull android.os.Bundle);
method public void stopRecording();
@@ -27424,6 +27541,7 @@
method public void timeShiftPlay(String, android.net.Uri);
method public void timeShiftResume();
method public void timeShiftSeekTo(long);
+ method public void timeShiftSetMode(int);
method public void timeShiftSetPlaybackParams(@NonNull android.media.PlaybackParams);
method public void tune(@NonNull String, android.net.Uri);
method public void tune(String, android.net.Uri, android.os.Bundle);
@@ -27444,12 +27562,15 @@
method public void onAitInfoUpdated(@NonNull String, @NonNull android.media.tv.AitInfo);
method public void onAudioPresentationSelected(@NonNull String, int, int);
method public void onAudioPresentationsChanged(@NonNull String, @NonNull java.util.List<android.media.AudioPresentation>);
+ method public void onAvailableSpeeds(@NonNull String, @NonNull float[]);
method public void onChannelRetuned(String, android.net.Uri);
method public void onConnectionFailed(String);
method public void onContentAllowed(String);
method public void onContentBlocked(String, android.media.tv.TvContentRating);
+ method public void onCueingMessageAvailability(@NonNull String, boolean);
method public void onDisconnected(String);
method public void onSignalStrengthUpdated(@NonNull String, int);
+ method public void onTimeShiftMode(@NonNull String, int);
method public void onTimeShiftStatusChanged(String, int);
method public void onTrackSelected(String, int, String);
method public void onTracksChanged(String, java.util.List<android.media.tv.TvTrackInfo>);
@@ -27474,6 +27595,7 @@
}
public final class TvInteractiveAppManager {
+ method @NonNull public java.util.List<android.media.tv.interactive.AppLinkInfo> getAppLinkInfoList();
method @NonNull public java.util.List<android.media.tv.interactive.TvInteractiveAppServiceInfo> getTvInteractiveAppServiceList();
method public void registerAppLinkInfo(@NonNull String, @NonNull android.media.tv.interactive.AppLinkInfo);
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback);
@@ -27531,7 +27653,11 @@
field public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY = "command_change_channel_quietly";
field public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
field public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
+ field public static final String COMMAND_PARAMETER_KEY_PLAYBACK_PARAMS = "command_playback_params";
+ field public static final String COMMAND_PARAMETER_KEY_PROGRAM_URI = "command_program_uri";
field public static final String COMMAND_PARAMETER_KEY_STOP_MODE = "command_stop_mode";
+ field public static final String COMMAND_PARAMETER_KEY_TIME_POSITION = "command_time_position";
+ field public static final String COMMAND_PARAMETER_KEY_TIME_SHIFT_MODE = "command_time_shift_mode";
field public static final String COMMAND_PARAMETER_KEY_TRACK_ID = "command_track_id";
field public static final String COMMAND_PARAMETER_KEY_TRACK_TYPE = "command_track_type";
field public static final String COMMAND_PARAMETER_KEY_VOLUME = "command_volume";
@@ -27545,6 +27671,12 @@
field public static final String PLAYBACK_COMMAND_TYPE_TUNE_PREV = "tune_previous";
field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvInteractiveAppService";
field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
+ field public static final String TIME_SHIFT_COMMAND_TYPE_PAUSE = "pause";
+ field public static final String TIME_SHIFT_COMMAND_TYPE_PLAY = "play";
+ field public static final String TIME_SHIFT_COMMAND_TYPE_RESUME = "resume";
+ field public static final String TIME_SHIFT_COMMAND_TYPE_SEEK_TO = "seek_to";
+ field public static final String TIME_SHIFT_COMMAND_TYPE_SET_MODE = "set_mode";
+ field public static final String TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS = "set_playback_params";
}
public abstract static class TvInteractiveAppService.Session implements android.view.KeyEvent.Callback {
@@ -27557,6 +27689,7 @@
method @CallSuper public final void notifyTeletextAppStateChanged(int);
method public void onAdBufferConsumed(@NonNull android.media.tv.AdBuffer);
method public void onAdResponse(@NonNull android.media.tv.AdResponse);
+ method public void onAvailableSpeeds(@NonNull float[]);
method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
method public void onContentAllowed();
method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
@@ -27574,8 +27707,13 @@
method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
method public void onMediaViewSizeChanged(@Px int, @Px int);
- method public void onRecordingStarted(@NonNull String);
+ method public void onRecordingConnectionFailed(@NonNull String, @NonNull String);
+ method public void onRecordingDisconnected(@NonNull String, @NonNull String);
+ method public void onRecordingError(@NonNull String, int);
+ method public void onRecordingScheduled(@NonNull String, @Nullable String);
+ method public void onRecordingStarted(@NonNull String, @Nullable String);
method public void onRecordingStopped(@NonNull String);
+ method public void onRecordingTuned(@NonNull String, @NonNull android.net.Uri);
method public abstract void onRelease();
method public void onResetInteractiveApp();
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -27586,6 +27724,11 @@
method public void onStopInteractiveApp();
method public void onStreamVolume(float);
method public void onSurfaceChanged(int, int, int);
+ method public void onTimeShiftCurrentPositionChanged(@NonNull String, long);
+ method public void onTimeShiftMode(int);
+ method public void onTimeShiftPlaybackParams(@NonNull android.media.PlaybackParams);
+ method public void onTimeShiftStartPositionChanged(@NonNull String, long);
+ method public void onTimeShiftStatusChanged(@NonNull String, int);
method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
method public void onTrackInfoList(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
method public void onTrackSelected(int, @NonNull String);
@@ -27599,19 +27742,24 @@
method public void onVideoUnavailable(int);
method @CallSuper public void removeBroadcastInfo(int);
method @CallSuper public void requestAd(@NonNull android.media.tv.AdRequest);
+ method @CallSuper public void requestAvailableSpeeds();
method @CallSuper public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
method @CallSuper public void requestCurrentChannelLcn();
method @CallSuper public void requestCurrentChannelUri();
method @CallSuper public void requestCurrentTvInputId();
method @CallSuper public void requestCurrentVideoBounds();
+ method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
+ method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
- method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
+ method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
method @CallSuper public void requestStopRecording(@NonNull String);
method @CallSuper public void requestStreamVolume();
+ method @CallSuper public void requestTimeShiftMode();
method @CallSuper public void requestTrackInfoList();
method @CallSuper public void requestTvRecordingInfo(@NonNull String);
method @CallSuper public void requestTvRecordingInfoList(@NonNull int);
method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
+ method @CallSuper public void sendTimeShiftCommandRequest(@NonNull String, @Nullable android.os.Bundle);
method @CallSuper public void setMediaViewEnabled(boolean);
method @CallSuper public void setTvRecordingInfo(@NonNull String, @NonNull android.media.tv.TvRecordingInfo);
method @CallSuper public void setVideoBounds(@NonNull android.graphics.Rect);
@@ -27620,6 +27768,7 @@
public final class TvInteractiveAppServiceInfo implements android.os.Parcelable {
ctor public TvInteractiveAppServiceInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName);
method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getCustomSupportedTypes();
method @NonNull public String getId();
method @Nullable public android.content.pm.ServiceInfo getServiceInfo();
method @NonNull public int getSupportedTypes();
@@ -27628,6 +27777,8 @@
field public static final int INTERACTIVE_APP_TYPE_ATSC = 2; // 0x2
field public static final int INTERACTIVE_APP_TYPE_GINGA = 4; // 0x4
field public static final int INTERACTIVE_APP_TYPE_HBBTV = 1; // 0x1
+ field public static final int INTERACTIVE_APP_TYPE_OTHER = -2147483648; // 0x80000000
+ field public static final int INTERACTIVE_APP_TYPE_TARGETED_AD = 8; // 0x8
}
public class TvInteractiveAppView extends android.view.ViewGroup {
@@ -27641,8 +27792,13 @@
method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
- method public void notifyRecordingStarted(@NonNull String);
+ method public void notifyRecordingScheduled(@NonNull String, @Nullable String);
+ method public void notifyRecordingStarted(@NonNull String, @Nullable String);
method public void notifyRecordingStopped(@NonNull String);
+ method public void notifyTimeShiftCurrentPositionChanged(@NonNull String, long);
+ method public void notifyTimeShiftPlaybackParams(@NonNull android.media.PlaybackParams);
+ method public void notifyTimeShiftStartPositionChanged(@NonNull String, long);
+ method public void notifyTimeShiftStatusChanged(@NonNull String, int);
method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
@@ -27653,12 +27809,14 @@
method public void prepareInteractiveApp(@NonNull String, int);
method public void reset();
method public void resetInteractiveApp();
+ method public void sendAvailableSpeeds(@NonNull float[]);
method public void sendCurrentChannelLcn(int);
method public void sendCurrentChannelUri(@Nullable android.net.Uri);
method public void sendCurrentTvInputId(@Nullable String);
method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendStreamVolume(float);
+ method public void sendTimeShiftMode(int);
method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
method public void sendTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
method public void sendTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
@@ -27684,14 +27842,18 @@
ctor public TvInteractiveAppView.TvInteractiveAppCallback();
method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+ method public void onRequestAvailableSpeeds(@NonNull String);
method public void onRequestCurrentChannelLcn(@NonNull String);
method public void onRequestCurrentChannelUri(@NonNull String);
method public void onRequestCurrentTvInputId(@NonNull String);
method public void onRequestCurrentVideoBounds(@NonNull String);
+ method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
+ method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
- method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
+ method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
method public void onRequestStopRecording(@NonNull String, @NonNull String);
method public void onRequestStreamVolume(@NonNull String);
+ method public void onRequestTimeShiftMode(@NonNull String);
method public void onRequestTrackInfoList(@NonNull String);
method public void onRequestTvRecordingInfo(@NonNull String, @NonNull String);
method public void onRequestTvRecordingInfoList(@NonNull String, @NonNull int);
@@ -27699,6 +27861,7 @@
method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
method public void onStateChanged(@NonNull String, int, int);
method public void onTeletextAppStateChanged(@NonNull String, int);
+ method public void onTimeShiftCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
}
}
@@ -33297,6 +33460,7 @@
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
+ field public static final String ACTION_LOW_POWER_STANDBY_POLICY_CHANGED = "android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED";
field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -41258,15 +41422,42 @@
method public default void onSegmentResults(@NonNull android.os.Bundle);
}
+ public final class RecognitionPart implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConfidenceLevel();
+ method @Nullable public String getFormattedText();
+ method @NonNull public String getRawText();
+ method public long getTimestampMillis();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONFIDENCE_LEVEL_HIGH = 5; // 0x5
+ field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1
+ field public static final int CONFIDENCE_LEVEL_LOW_MEDIUM = 2; // 0x2
+ field public static final int CONFIDENCE_LEVEL_MEDIUM = 3; // 0x3
+ field public static final int CONFIDENCE_LEVEL_MEDIUM_HIGH = 4; // 0x4
+ field public static final int CONFIDENCE_LEVEL_UNKNOWN = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.speech.RecognitionPart> CREATOR;
+ }
+
+ public static final class RecognitionPart.Builder {
+ ctor public RecognitionPart.Builder(@NonNull String);
+ method @NonNull public android.speech.RecognitionPart build();
+ method @NonNull public android.speech.RecognitionPart.Builder setConfidenceLevel(int);
+ method @NonNull public android.speech.RecognitionPart.Builder setFormattedText(@NonNull String);
+ method @NonNull public android.speech.RecognitionPart.Builder setRawText(@NonNull String);
+ method @NonNull public android.speech.RecognitionPart.Builder setTimestampMillis(long);
+ }
+
public abstract class RecognitionService extends android.app.Service {
ctor public RecognitionService();
method public int getMaxConcurrentSessionsCount();
method public final android.os.IBinder onBind(android.content.Intent);
method protected abstract void onCancel(android.speech.RecognitionService.Callback);
method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback);
+ method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.RecognitionService.SupportCallback);
method protected abstract void onStartListening(android.content.Intent, android.speech.RecognitionService.Callback);
method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
method public void onTriggerModelDownload(@NonNull android.content.Intent);
+ method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
field public static final String SERVICE_META_DATA = "android.speech";
}
@@ -41347,6 +41538,8 @@
field public static final String EXTRA_PARTIAL_RESULTS = "android.speech.extra.PARTIAL_RESULTS";
field public static final String EXTRA_PREFER_OFFLINE = "android.speech.extra.PREFER_OFFLINE";
field public static final String EXTRA_PROMPT = "android.speech.extra.PROMPT";
+ field public static final String EXTRA_REQUEST_WORD_CONFIDENCE = "android.speech.extra.REQUEST_WORD_CONFIDENCE";
+ field public static final String EXTRA_REQUEST_WORD_TIMING = "android.speech.extra.REQUEST_WORD_TIMING";
field public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
field public static final String EXTRA_RESULTS_PENDINGINTENT = "android.speech.extra.RESULTS_PENDINGINTENT";
field public static final String EXTRA_RESULTS_PENDINGINTENT_BUNDLE = "android.speech.extra.RESULTS_PENDINGINTENT_BUNDLE";
@@ -41406,6 +41599,7 @@
field public static final int ERROR_SERVER_DISCONNECTED = 11; // 0xb
field public static final int ERROR_SPEECH_TIMEOUT = 6; // 0x6
field public static final int ERROR_TOO_MANY_REQUESTS = 10; // 0xa
+ field public static final String RECOGNITION_PARTS = "recognition_parts";
field public static final String RESULTS_ALTERNATIVES = "results_alternatives";
field public static final String RESULTS_RECOGNITION = "results_recognition";
}
@@ -41933,18 +42127,6 @@
method public android.telecom.CallScreeningService.CallResponse.Builder setSkipNotification(boolean);
}
- public abstract class CallStreamingService extends android.app.Service {
- ctor public CallStreamingService();
- method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
- method public void onCallStreamingStarted(@NonNull android.telecom.StreamingCall);
- method public void onCallStreamingStateChanged(int);
- method public void onCallStreamingStopped();
- field public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
- field public static final int STREAMING_FAILED_ALREADY_STREAMING = 1; // 0x1
- field public static final int STREAMING_FAILED_NO_SENDER = 2; // 0x2
- field public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3; // 0x3
- }
-
public abstract class Conference extends android.telecom.Conferenceable {
ctor public Conference(android.telecom.PhoneAccountHandle);
method public final boolean addConnection(android.telecom.Connection);
@@ -42617,22 +42799,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
}
- public final class StreamingCall implements android.os.Parcelable {
- ctor public StreamingCall(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.net.Uri, @NonNull android.os.Bundle);
- method public int describeContents();
- method @NonNull public android.net.Uri getAddress();
- method @NonNull public android.content.ComponentName getComponentName();
- method @NonNull public String getDisplayName();
- method @NonNull public android.os.Bundle getExtras();
- method public int getState();
- method public void requestStreamingState(int);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
- field public static final int STATE_DISCONNECTED = 3; // 0x3
- field public static final int STATE_HOLDING = 2; // 0x2
- field public static final int STATE_STREAMING = 1; // 0x1
- }
-
public class TelecomManager {
method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
@@ -49919,6 +50085,7 @@
method public int getDisplayId();
method public int getFlags();
method public android.view.Display.HdrCapabilities getHdrCapabilities();
+ method public float getHdrSdrRatio();
method @Deprecated public int getHeight();
method @Deprecated public void getMetrics(android.util.DisplayMetrics);
method public android.view.Display.Mode getMode();
@@ -49940,9 +50107,12 @@
method @Deprecated public float[] getSupportedRefreshRates();
method @Deprecated public int getWidth();
method public boolean isHdr();
+ method public boolean isHdrSdrRatioAvailable();
method public boolean isMinimalPostProcessingSupported();
method public boolean isValid();
method public boolean isWideColorGamut();
+ method public void registerHdrSdrRatioChangedListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.Display>);
+ method public void unregisterHdrSdrRatioChangedListener(@NonNull java.util.function.Consumer<android.view.Display>);
field public static final int DEFAULT_DISPLAY = 0; // 0x0
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
@@ -51471,6 +51641,7 @@
method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
+ method @NonNull public android.view.SurfaceControl.Transaction setExtendedRangeBrightness(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
@@ -53624,12 +53795,14 @@
method public int getFitInsetsTypes();
method public final CharSequence getTitle();
method public boolean isFitInsetsIgnoringVisibility();
+ method public boolean isHdrConversionEnabled();
method public static boolean mayUseInputMethod(int);
method public void setBlurBehindRadius(@IntRange(from=0) int);
method public void setColorMode(int);
method public void setFitInsetsIgnoringVisibility(boolean);
method public void setFitInsetsSides(int);
method public void setFitInsetsTypes(int);
+ method public void setHdrConversionEnabled(boolean);
method public final void setTitle(CharSequence);
method public void setWallpaperTouchEventsEnabled(boolean);
method public void writeToParcel(android.os.Parcel, int);
@@ -53640,6 +53813,7 @@
field public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
field @NonNull public static final android.os.Parcelable.Creator<android.view.WindowManager.LayoutParams> CREATOR;
field public static final int DIM_AMOUNT_CHANGED = 32; // 0x20
+ field public static final int DISPLAY_FLAG_DISABLE_HDR_CONVERSION = 1; // 0x1
field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 5171027..1e21c776 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -166,6 +166,7 @@
method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp();
method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio();
+ method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeBroadcast();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BluetoothProfileConnectionInfo);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void setA2dpSuspended(boolean);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean);
@@ -462,6 +463,25 @@
method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static java.util.Map<java.lang.String,java.util.List<android.content.ContentValues>> queryRawContactEntity(@NonNull android.content.ContentResolver, long);
}
+ public class DeviceConfigInitializer {
+ method @Nullable public static android.provider.DeviceConfigServiceManager getDeviceConfigServiceManager();
+ method public static void setDeviceConfigServiceManager(@NonNull android.provider.DeviceConfigServiceManager);
+ }
+
+ public class DeviceConfigServiceManager {
+ method @NonNull public android.provider.DeviceConfigServiceManager.ServiceRegisterer getDeviceConfigUpdatableServiceRegisterer();
+ }
+
+ public static class DeviceConfigServiceManager.ServiceNotFoundException extends java.lang.Exception {
+ }
+
+ public static final class DeviceConfigServiceManager.ServiceRegisterer {
+ method @Nullable public android.os.IBinder get();
+ method @NonNull public android.os.IBinder getOrThrow() throws android.provider.DeviceConfigServiceManager.ServiceNotFoundException;
+ method public void register(@NonNull android.os.IBinder);
+ method @Nullable public android.os.IBinder tryGet();
+ }
+
public final class Settings {
field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
field public static final int RESET_MODE_TRUSTED_DEFAULTS = 4; // 0x4
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index dc7e28c..ba9663f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -46,6 +46,7 @@
field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE";
+ field public static final String BIND_CALL_STREAMING_SERVICE = "android.permission.BIND_CALL_STREAMING_SERVICE";
field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
@@ -174,6 +175,7 @@
field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
+ field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
@@ -275,6 +277,7 @@
field public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE";
+ field public static final String READ_RESTRICTED_STATS = "android.permission.READ_RESTRICTED_STATS";
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
@@ -523,6 +526,7 @@
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
+ method @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String);
method @NonNull public java.util.Collection<java.util.Locale> getSupportedLocales();
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
@@ -1202,11 +1206,19 @@
package android.app.admin {
+ public final class AccountTypePolicyKey extends android.app.admin.PolicyKey {
+ method public int describeContents();
+ method @NonNull public String getAccountType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.AccountTypePolicyKey> CREATOR;
+ }
+
public abstract class Authority implements android.os.Parcelable {
method public int describeContents();
}
public final class DeviceAdminAuthority extends android.app.admin.Authority {
+ ctor public DeviceAdminAuthority();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminAuthority> CREATOR;
}
@@ -1287,6 +1299,8 @@
field public static final int EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = 2; // 0x2
field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
field public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS = 1; // 0x1
+ field public static final int EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION = 4; // 0x4
+ field public static final int EXEMPT_FROM_HIBERNATION = 3; // 0x3
field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1389,11 +1403,13 @@
}
public final class DpcAuthority extends android.app.admin.Authority {
+ ctor public DpcAuthority();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DpcAuthority> CREATOR;
}
public final class EnforcingAdmin implements android.os.Parcelable {
+ ctor public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle);
method public int describeContents();
method @NonNull public android.app.admin.Authority getAuthority();
method @NonNull public String getPackageName();
@@ -1517,6 +1533,7 @@
}
public final class RoleAuthority extends android.app.admin.Authority {
+ ctor public RoleAuthority(@NonNull java.util.Set<java.lang.String>);
method @NonNull public java.util.Set<java.lang.String> getRoles();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.RoleAuthority> CREATOR;
@@ -1537,10 +1554,18 @@
}
public final class UnknownAuthority extends android.app.admin.Authority {
+ ctor public UnknownAuthority();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnknownAuthority> CREATOR;
}
+ public final class UserRestrictionPolicyKey extends android.app.admin.PolicyKey {
+ method public int describeContents();
+ method @NonNull public String getRestriction();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UserRestrictionPolicyKey> CREATOR;
+ }
+
}
package android.app.ambientcontext {
@@ -2258,7 +2283,6 @@
method public void notifyEvent(@NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
method @Nullable public void query(@NonNull android.app.search.Query, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
method public void registerEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
- method public void requestEmptyQueryResultUpdate();
method public void unregisterEmptyQueryResultUpdateCallback(@NonNull android.app.search.SearchSession.Callback);
}
@@ -3186,7 +3210,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
- method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
+ method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
@@ -3241,6 +3265,7 @@
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorCallback);
}
}
@@ -3292,14 +3317,18 @@
package android.companion.virtual.sensor {
- public class VirtualSensor {
+ public final class VirtualSensor implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getDeviceId();
method @NonNull public String getName();
method public int getType();
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensor> CREATOR;
}
- public static interface VirtualSensor.SensorStateChangeCallback {
- method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+ public interface VirtualSensorCallback {
+ method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
}
public final class VirtualSensorConfig implements android.os.Parcelable {
@@ -3314,7 +3343,6 @@
public static final class VirtualSensorConfig.Builder {
ctor public VirtualSensorConfig.Builder(int, @NonNull String);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
- method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
}
@@ -5961,7 +5989,7 @@
method public boolean isHotPlugDetectActive();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.DisplayPortAltModeInfo> CREATOR;
- field public static final int DISPLAYPORT_ALT_MODE_STATUS_CAPABLE = 2; // 0x2
+ field public static final int DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED = 2; // 0x2
field public static final int DISPLAYPORT_ALT_MODE_STATUS_ENABLED = 3; // 0x3
field public static final int DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE = 1; // 0x1
field public static final int DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN = 0; // 0x0
@@ -5979,7 +6007,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String);
method public static boolean isUvcSupportEnabled();
- method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public boolean registerDisplayPortAltModeInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void registerDisplayPortAltModeInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener);
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbGadget();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void unregisterDisplayPortAltModeInfoListener(@NonNull android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener);
@@ -9832,11 +9860,15 @@
method public int getMin5gRssiDbm();
method public int getMin6gRssiDbm();
method @NonNull public java.util.List<android.net.wifi.nl80211.PnoNetwork> getPnoNetworks();
+ method public int getScanIntervalMultiplier();
+ method public int getScanIterations();
method public void setIntervalMillis(long);
method public void setMin2gRssiDbm(int);
method public void setMin5gRssiDbm(int);
method public void setMin6gRssiDbm(int);
method public void setPnoNetworks(@NonNull java.util.List<android.net.wifi.nl80211.PnoNetwork>);
+ method public void setScanIntervalMultiplier(int);
+ method public void setScanIterations(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.nl80211.PnoSettings> CREATOR;
}
@@ -9987,6 +10019,7 @@
public final class KnownNetworkConnectionStatus implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork getKnownNetwork();
method public int getStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CONNECTION_STATUS_SAVED = 1; // 0x1
@@ -9999,6 +10032,7 @@
ctor public KnownNetworkConnectionStatus.Builder();
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus build();
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.Builder setKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.Builder setStatus(int);
}
@@ -10067,6 +10101,7 @@
method public int describeContents();
method @NonNull public android.os.Bundle getExtras();
method public int getStatus();
+ method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork getTetherNetwork();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED = 9; // 0x9
field public static final int CONNECTION_STATUS_ENABLING_HOTSPOT = 1; // 0x1
@@ -10086,6 +10121,7 @@
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus build();
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.Builder setStatus(int);
+ method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.Builder setTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork);
}
}
@@ -10635,7 +10671,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
- field @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) public static final String ACTION_LOW_POWER_STANDBY_POLICY_CHANGED = "android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED";
field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
field public static final String REBOOT_USERSPACE = "userspace";
@@ -12445,7 +12480,7 @@
method public final void adjustNotification(@NonNull android.service.notification.Adjustment);
method public final void adjustNotifications(@NonNull java.util.List<android.service.notification.Adjustment>);
method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public void onAllowedAdjustmentsChanged();
+ method @Deprecated public void onAllowedAdjustmentsChanged();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
method public void onNotificationClicked(@NonNull String);
method public void onNotificationDirectReplied(@NonNull String);
@@ -12659,7 +12694,6 @@
method @MainThread public abstract void onDestroy(@NonNull android.app.search.SearchSessionId);
method @MainThread public abstract void onNotifyEvent(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
method @MainThread public abstract void onQuery(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
- method @MainThread public void onRequestEmptyQueryResultUpdate(@NonNull android.app.search.SearchSessionId);
method public void onSearchSessionCreated(@NonNull android.app.search.SearchContext, @NonNull android.app.search.SearchSessionId);
method @MainThread public void onStartUpdateEmptyQueryResult();
method @MainThread public void onStopUpdateEmptyQueryResult();
@@ -13298,6 +13332,19 @@
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public android.telecom.CallScreeningService.CallResponse.Builder setShouldScreenCallViaAudioProcessing(boolean);
}
+ public abstract class CallStreamingService extends android.app.Service {
+ ctor public CallStreamingService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onCallStreamingStarted(@NonNull android.telecom.StreamingCall);
+ method public void onCallStreamingStateChanged(int);
+ method public void onCallStreamingStopped();
+ field public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+ field public static final int STREAMING_FAILED_ALREADY_STREAMING = 1; // 0x1
+ field public static final int STREAMING_FAILED_NO_SENDER = 2; // 0x2
+ field public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3; // 0x3
+ field public static final int STREAMING_FAILED_UNKNOWN = 0; // 0x0
+ }
+
public abstract class Conference extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
method @Deprecated public final long getConnectTimeMillis();
@@ -13521,6 +13568,22 @@
method @Deprecated public android.content.ComponentName getPackageName();
}
+ public final class StreamingCall implements android.os.Parcelable {
+ ctor public StreamingCall(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.net.Uri, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public android.net.Uri getAddress();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @NonNull public CharSequence getDisplayName();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getState();
+ method public void requestStreamingState(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
+ field public static final int STATE_DISCONNECTED = 3; // 0x3
+ field public static final int STATE_HOLDING = 2; // 0x2
+ field public static final int STATE_STREAMING = 1; // 0x1
+ }
+
public final class TelecomAnalytics implements android.os.Parcelable {
ctor public TelecomAnalytics(java.util.List<android.telecom.TelecomAnalytics.SessionTiming>, java.util.List<android.telecom.ParcelableCallAnalytics>);
method public int describeContents();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c25f875..bb3ea7b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -349,9 +349,7 @@
}
public class NotificationManager {
- method public void allowAssistantAdjustment(String);
method public void cleanUpCallersAfter(long);
- method public void disallowAssistantAdjustment(String);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
@@ -514,6 +512,10 @@
package android.app.admin {
+ public final class DeviceAdminAuthority extends android.app.admin.Authority {
+ field @NonNull public static final android.app.admin.DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY;
+ }
+
public class DevicePolicyManager {
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
@@ -594,21 +596,28 @@
field @Deprecated public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
}
+ public final class DpcAuthority extends android.app.admin.Authority {
+ field @NonNull public static final android.app.admin.DpcAuthority DPC_AUTHORITY;
+ }
+
public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> {
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FlagUnion> CREATOR;
+ field @NonNull public static final android.app.admin.FlagUnion FLAG_UNION;
}
public final class MostRecent<V> extends android.app.admin.ResolutionMechanism<V> {
ctor public MostRecent();
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRecent> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRecent<?>> CREATOR;
+ field @NonNull public static final android.app.admin.MostRecent<?> MOST_RECENT;
}
public final class MostRestrictive<V> extends android.app.admin.ResolutionMechanism<V> {
method public int describeContents();
+ method @NonNull public java.util.List<V> getMostToLeastRestrictiveValues();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRestrictive<?>> CREATOR;
}
@@ -629,14 +638,20 @@
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.StringSetUnion> CREATOR;
+ field @NonNull public static final android.app.admin.StringSetUnion STRING_SET_UNION;
}
public final class TopPriority<V> extends android.app.admin.ResolutionMechanism<V> {
method public int describeContents();
+ method @NonNull public java.util.List<android.app.admin.Authority> getHighestToLowestPriorityAuthorities();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.TopPriority<?>> CREATOR;
}
+ public final class UnknownAuthority extends android.app.admin.Authority {
+ field @NonNull public static final android.app.admin.UnknownAuthority UNKNOWN_AUTHORITY;
+ }
+
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
ctor public UnsafeStateException(int, int);
method public int getOperation();
@@ -1402,6 +1417,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode();
method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode();
method @NonNull public android.hardware.display.HdrConversionMode getHdrConversionMode();
+ method @NonNull public android.hardware.display.HdrConversionMode getHdrConversionModeSetting();
method @NonNull public int[] getSupportedHdrOutputTypes();
method @NonNull public int[] getUserDisabledHdrTypes();
method public boolean isMinimalPostProcessingRequested(int);
@@ -1481,10 +1497,13 @@
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@FloatRange(from=0, to=1) float);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
+ public class InputSettings {
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
+ }
+
}
package android.hardware.lights {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3c17a33..1d03d84 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -26,8 +26,10 @@
import static java.lang.Character.MIN_VALUE;
+import android.annotation.AnimRes;
import android.annotation.CallSuper;
import android.annotation.CallbackExecutor;
+import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.IntDef;
@@ -108,6 +110,7 @@
import android.util.Dumpable;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -1006,6 +1009,25 @@
*/
public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
+ /** @hide */
+ @IntDef(prefix = { "OVERRIDE_TRANSITION_" }, value = {
+ OVERRIDE_TRANSITION_OPEN,
+ OVERRIDE_TRANSITION_CLOSE
+ })
+ public @interface OverrideTransition {}
+
+ /**
+ * Request type of {@link #overrideActivityTransition(int, int, int)} or
+ * {@link #overrideActivityTransition(int, int, int, int)}, to override the
+ * opening transition.
+ */
+ public static final int OVERRIDE_TRANSITION_OPEN = 0;
+ /**
+ * Request type of {@link #overrideActivityTransition(int, int, int)} or
+ * {@link #overrideActivityTransition(int, int, int, int)}, to override the
+ * closing transition.
+ */
+ public static final int OVERRIDE_TRANSITION_CLOSE = 1;
private boolean mShouldDockBigOverlays;
private UiTranslationController mUiTranslationController;
@@ -6462,6 +6484,124 @@
}
/**
+ * Customizes the animation for the activity transition with this activity. This can be called
+ * at any time while the activity still alive.
+ *
+ * <p> This is a more robust method of overriding the transition animation at runtime without
+ * relying on {@link #overridePendingTransition(int, int)} which doesn't work for predictive
+ * back. However, the animation set from {@link #overridePendingTransition(int, int)} still
+ * has higher priority when the system is looking for the next transition animation.</p>
+ * <p> The animations resources set by this method will be chosen if and only if the activity is
+ * on top of the task while activity transitions are being played.
+ * For example, if we want to customize the opening transition when launching Activity B which
+ * gets started from Activity A, we should call this method inside B's onCreate with
+ * {@code overrideType = OVERRIDE_TRANSITION_OPEN} because the Activity B will on top of the
+ * task. And if we want to customize the closing transition when finishing Activity B and back
+ * to Activity A, since B is still is above A, we should call this method in Activity B with
+ * {@code overrideType = OVERRIDE_TRANSITION_CLOSE}. </p>
+ *
+ * <p> If an Activity has called this method, and it also set another activity animation
+ * by {@link Window#setWindowAnimations(int)}, the system will choose the animation set from
+ * this method.</p>
+ *
+ * <p> Note that {@link Window#setWindowAnimations},
+ * {@link #overridePendingTransition(int, int)} and this method will be ignored if the Activity
+ * is started with {@link ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])}. Also
+ * note that this method can only be used to customize cross-activity transitions but not
+ * cross-task transitions which are fully non-customizable as of Android 11.</p>
+ *
+ * @param overrideType {@code OVERRIDE_TRANSITION_OPEN} This animation will be used when
+ * starting/entering an activity. {@code OVERRIDE_TRANSITION_CLOSE} This
+ * animation will be used when finishing/closing an activity.
+ * @param enterAnim A resource ID of the animation resource to use for the incoming activity.
+ * Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for the outgoing activity.
+ * Use 0 for no animation.
+ *
+ * @see #overrideActivityTransition(int, int, int, int)
+ * @see #clearOverrideActivityTransition(int)
+ * @see OnBackInvokedCallback
+ * @see #overridePendingTransition(int, int)
+ * @see Window#setWindowAnimations(int)
+ * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
+ */
+ public void overrideActivityTransition(@OverrideTransition int overrideType,
+ @AnimRes int enterAnim, @AnimRes int exitAnim) {
+ overrideActivityTransition(overrideType, enterAnim, exitAnim, Color.TRANSPARENT);
+ }
+
+ /**
+ * Customizes the animation for the activity transition with this activity. This can be called
+ * at any time while the activity still alive.
+ *
+ * <p> This is a more robust method of overriding the transition animation at runtime without
+ * relying on {@link #overridePendingTransition(int, int)} which doesn't work for predictive
+ * back. However, the animation set from {@link #overridePendingTransition(int, int)} still
+ * has higher priority when the system is looking for the next transition animation.</p>
+ * <p> The animations resources set by this method will be chosen if and only if the activity is
+ * on top of the task while activity transitions are being played.
+ * For example, if we want to customize the opening transition when launching Activity B which
+ * gets started from Activity A, we should call this method inside B's onCreate with
+ * {@code overrideType = OVERRIDE_TRANSITION_OPEN} because the Activity B will on top of the
+ * task. And if we want to customize the closing transition when finishing Activity B and back
+ * to Activity A, since B is still is above A, we should call this method in Activity B with
+ * {@code overrideType = OVERRIDE_TRANSITION_CLOSE}. </p>
+ *
+ * <p> If an Activity has called this method, and it also set another activity animation
+ * by {@link Window#setWindowAnimations(int)}, the system will choose the animation set from
+ * this method.</p>
+ *
+ * <p> Note that {@link Window#setWindowAnimations},
+ * {@link #overridePendingTransition(int, int)} and this method will be ignored if the Activity
+ * is started with {@link ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])}. Also
+ * note that this method can only be used to customize cross-activity transitions but not
+ * cross-task transitions which are fully non-customizable as of Android 11.</p>
+ *
+ * @param overrideType {@code OVERRIDE_TRANSITION_OPEN} This animation will be used when
+ * starting/entering an activity. {@code OVERRIDE_TRANSITION_CLOSE} This
+ * animation will be used when finishing/closing an activity.
+ * @param enterAnim A resource ID of the animation resource to use for the incoming activity.
+ * Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for the outgoing activity.
+ * Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation
+ * if the animation requires a background. Set to
+ * {@link Color#TRANSPARENT} to not override the default color.
+ * @see #overrideActivityTransition(int, int, int)
+ * @see #clearOverrideActivityTransition(int)
+ * @see OnBackInvokedCallback
+ * @see #overridePendingTransition(int, int)
+ * @see Window#setWindowAnimations(int)
+ * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
+ */
+ public void overrideActivityTransition(@OverrideTransition int overrideType,
+ @AnimRes int enterAnim, @AnimRes int exitAnim, @ColorInt int backgroundColor) {
+ if (overrideType != OVERRIDE_TRANSITION_OPEN && overrideType != OVERRIDE_TRANSITION_CLOSE) {
+ throw new IllegalArgumentException("Override type must be either open or close");
+ }
+
+ ActivityClient.getInstance().overrideActivityTransition(mToken,
+ overrideType == OVERRIDE_TRANSITION_OPEN, enterAnim, exitAnim, backgroundColor);
+ }
+
+ /**
+ * Clears the animations which are set from {@link #overrideActivityTransition}.
+ * @param overrideType {@code OVERRIDE_TRANSITION_OPEN} clear the animation set for starting a
+ * new activity. {@code OVERRIDE_TRANSITION_CLOSE} clear the animation set
+ * for finishing an activity.
+ *
+ * @see #overrideActivityTransition(int, int, int)
+ * @see #overrideActivityTransition(int, int, int, int)
+ */
+ public void clearOverrideActivityTransition(@OverrideTransition int overrideType) {
+ if (overrideType != OVERRIDE_TRANSITION_OPEN && overrideType != OVERRIDE_TRANSITION_CLOSE) {
+ throw new IllegalArgumentException("Override type must be either open or close");
+ }
+ ActivityClient.getInstance().clearOverrideActivityTransition(mToken,
+ overrideType == OVERRIDE_TRANSITION_OPEN);
+ }
+
+ /**
* Call immediately after one of the flavors of {@link #startActivity(Intent)}
* or {@link #finish} to specify an explicit transition animation to
* perform next.
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index aa868a7..5354a44 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -486,6 +486,24 @@
}
}
+ void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim,
+ int backgroundColor) {
+ try {
+ getActivityClientController().overrideActivityTransition(
+ token, open, enterAnim, exitAnim, backgroundColor);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void clearOverrideActivityTransition(IBinder token, boolean open) {
+ try {
+ getActivityClientController().clearOverrideActivityTransition(token, open);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim,
int backgroundColor) {
try {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f408916..42d056c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3676,6 +3676,123 @@
}
/**
+ * Return a list of {@link ApplicationStartInfo} records containing the information about the
+ * most recent app startups.
+ *
+ * <p class="note"> Note: System stores this historical information in a ring buffer and only
+ * the most recent records will be returned. </p>
+ *
+ * @param maxNum The maximum number of results to be returned; a value of 0
+ * means to ignore this parameter and return all matching records. If fewer
+ * records exist, all existing records will be returned.
+ *
+ * @return a list of {@link ApplicationStartInfo} records matching the criteria, sorted in
+ * the order from most recent to least recent.
+ */
+ @NonNull
+ public List<ApplicationStartInfo> getHistoricalProcessStartReasons(
+ @IntRange(from = 0) int maxNum) {
+ try {
+ ParceledListSlice<ApplicationStartInfo> startInfos = getService()
+ .getHistoricalProcessStartReasons(null, maxNum, mContext.getUserId());
+ return startInfos == null ? Collections.emptyList() : startInfos.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of {@link ApplicationStartInfo} records containing the information about the
+ * most recent app startups.
+ *
+ * <p class="note"> Note: System stores this historical information in a ring buffer and only
+ * the most recent records will be returned. </p>
+ *
+ * @param packageName Package name for which app startups to receive.
+ * @param maxNum The maximum number of results to be returned; a value of 0
+ * means to ignore this parameter and return all matching records. If fewer
+ * records exist, all existing records will be returned.
+ *
+ * @return a list of {@link ApplicationStartInfo} records matching the criteria, sorted in
+ * the order from most recent to least recent.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DUMP)
+ public List<ApplicationStartInfo> getExternalHistoricalProcessStartReasons(
+ @NonNull String packageName, @IntRange(from = 0) int maxNum) {
+ try {
+ ParceledListSlice<ApplicationStartInfo> startInfos = getService()
+ .getHistoricalProcessStartReasons(packageName, maxNum, mContext.getUserId());
+ return startInfos == null ? Collections.emptyList() : startInfos.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to receive {@link ApplicationStartInfo} object once recording of startup related
+ * metrics is complete.
+ * Use with {@link #setApplicationStartInfoCompleteListener}.
+ */
+ public interface ApplicationStartInfoCompleteListener {
+ /** {@link ApplicationStartInfo} is complete, no more info will be added. */
+ void onApplicationStartInfoComplete(@NonNull ApplicationStartInfo applicationStartInfo);
+ }
+
+ /**
+ * Sets a callback to be notified when the {@link ApplicationStartInfo} records of this startup
+ * are complete.
+ *
+ * <p class="note"> Note: callback will not wait for {@link Activity#reportFullyDrawn} to occur.
+ * Timestamp for fully drawn may be added after callback occurs. Set callback after invoking
+ * {@link Activity#reportFullyDrawn} if timestamp for fully drawn is required.</p>
+ *
+ * <p class="note"> Note: if start records have already been retrieved, the callback will be
+ * invoked immediately on the specified executor with the previously resolved AppStartInfo.</p>
+ *
+ * <p class="note"> Note: callback is asynchronous and should be made from a background thread.
+ * </p>
+ *
+ * @param executor The executor on which the listener should be called.
+ * @param listener Callback to be called when collection of {@link ApplicationStartInfo} is
+ * complete. Will replace existing listener if one is already attached.
+ *
+ * @throws IllegalArgumentException if executor or listener are null.
+ */
+ public void setApplicationStartInfoCompleteListener(@NonNull final Executor executor,
+ @NonNull final ApplicationStartInfoCompleteListener listener) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ IApplicationStartInfoCompleteListener callback =
+ new IApplicationStartInfoCompleteListener.Stub() {
+ @Override
+ public void onApplicationStartInfoComplete(ApplicationStartInfo applicationStartInfo) {
+ executor.execute(() ->
+ listener.onApplicationStartInfoComplete(applicationStartInfo));
+ }
+ };
+ try {
+ getService().setApplicationStartInfoCompleteListener(callback, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the callback set by {@link #setApplicationStartInfoCompleteListener} if there is one.
+ */
+ public void removeApplicationStartInfoCompleteListener() {
+ try {
+ getService().removeApplicationStartInfoCompleteListener(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return a list of {@link ApplicationExitInfo} records containing the reasons for the most
* recent app deaths.
*
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 324100f..e654c38 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -464,6 +464,11 @@
public abstract boolean isActivityStartsLoggingEnabled();
/** Returns true if the background activity starts is enabled. */
public abstract boolean isBackgroundActivityStartsEnabled();
+ /**
+ * Returns The current {@link BackgroundStartPrivileges} of the UID.
+ */
+ @NonNull
+ public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid);
public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
/** @see com.android.server.am.ActivityManagerService#monitor */
@@ -969,4 +974,12 @@
* @hide
*/
public abstract boolean canHoldWakeLocksInDeepDoze(int uid, int procstate);
+
+ /**
+ * Same as {@link android.app.IActivityManager#startProfile(int userId)}, but it would succeed
+ * even if the profile is disabled - it should only be called by
+ * {@link com.android.server.devicepolicy.DevicePolicyManagerService} when starting a profile
+ * while it's being created.
+ */
+ public abstract boolean startProfileEvenWhenDisabled(@UserIdInt int userId);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f78f452..543c5e7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -151,6 +151,8 @@
import android.provider.CalendarContract;
import android.provider.CallLog;
import android.provider.ContactsContract;
+import android.provider.DeviceConfigInitializer;
+import android.provider.DeviceConfigServiceManager;
import android.provider.Downloads;
import android.provider.FontsContract;
import android.provider.Settings;
@@ -8139,8 +8141,11 @@
MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
- BinderCallsStats.startForBluetooth(context); });
+ BinderCallsStats.startForBluetooth(context);
+ });
NfcFrameworkInitializer.setNfcServiceManager(new NfcServiceManager());
+
+ DeviceConfigInitializer.setDeviceConfigServiceManager(new DeviceConfigServiceManager());
}
private void purgePendingResources() {
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/ApplicationStartInfo.aidl b/core/java/android/app/ApplicationStartInfo.aidl
new file mode 100644
index 0000000..4322c7e
--- /dev/null
+++ b/core/java/android/app/ApplicationStartInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable ApplicationStartInfo;
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
new file mode 100644
index 0000000..794c55e
--- /dev/null
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Provide information related to a processes startup. */
+public final class ApplicationStartInfo implements Parcelable {
+
+ /**
+ * State indicating process startup has started. Some information is available in
+ * {@link ApplicationStartInfo} and more will be added.
+ */
+ public static final int STARTUP_STATE_STARTED = 0;
+
+ /**
+ * State indicating process startup has failed. Startup information in
+ * {@link ApplicationStartInfo} is incomplete, but no more will be added.
+ */
+ public static final int STARTUP_STATE_ERROR = 1;
+
+ /**
+ * State indicating process startup has made it to first frame draw. Startup
+ * information in {@link ApplicationStartInfo} is complete with potential exception
+ * of fully drawn timestamp which is not guaranteed to be set.
+ */
+ public static final int STARTUP_STATE_FIRST_FRAME_DRAWN = 2;
+
+ /** Process started due to alarm. */
+ public static final int START_REASON_ALARM = 0;
+
+ /** Process started to run backup. */
+ public static final int START_REASON_BACKUP = 1;
+
+ /** Process started due to boot complete. */
+ public static final int START_REASON_BOOT_COMPLETE = 2;
+
+ /** Process started due to broadcast received. */
+ public static final int START_REASON_BROADCAST = 3;
+
+ /** Process started due to access of ContentProvider */
+ public static final int START_REASON_CONTENT_PROVIDER = 4;
+
+ /** * Process started to run scheduled job. */
+ public static final int START_REASON_JOB = 5;
+
+ /** Process started due to click app icon or widget from launcher. */
+ public static final int START_REASON_LAUNCHER = 6;
+
+ /** Process started not for any of the listed reasons. */
+ public static final int START_REASON_OTHER = 7;
+
+ /** Process started due to push message. */
+ public static final int START_REASON_PUSH = 8;
+
+ /** Process started to resume activity. */
+ public static final int START_REASON_RESUMED_ACTIVITY = 9;
+
+ /** Process service started. */
+ public static final int START_REASON_SERVICE = 10;
+
+ /** Process started due to Activity started for any reason not explicitly listed. */
+ public static final int START_REASON_START_ACTIVITY = 11;
+
+ /** Process started from scratch. */
+ public static final int START_TYPE_COLD = 0;
+
+ /** Process retained minimally SavedInstanceState. */
+ public static final int START_TYPE_WARM = 1;
+
+ /** Process brought back to foreground. */
+ public static final int START_TYPE_HOT = 2;
+
+ /**
+ * Default. The system always creates a new instance of the activity in the target task and
+ * routes the intent to it.
+ */
+ public static final int LAUNCH_MODE_STANDARD = 0;
+
+ /**
+ * If an instance of the activity already exists at the top of the target task, the system
+ * routes the intent to that instance through a call to its onNewIntent() method, rather than
+ * creating a new instance of the activity.
+ */
+ public static final int LAUNCH_MODE_SINGLE_TOP = 1;
+
+ /**
+ * The system creates the activity at the root of a new task or locates the activity on an
+ * existing task with the same affinity. If an instance of the activity already exists and is at
+ * the root of the task, the system routes the intent to existing instance through a call to its
+ * onNewIntent() method, rather than creating a new one.
+ */
+ public static final int LAUNCH_MODE_SINGLE_INSTANCE = 2;
+
+ /**
+ * Same as "singleTask", except that the system doesn't launch any other activities into the
+ * task holding the instance. The activity is always the single and only member of its task.
+ */
+ public static final int LAUNCH_MODE_SINGLE_TASK = 3;
+
+ /**
+ * The activity can only be running as the root activity of the task, the first activity that
+ * created the task, and therefore there will only be one instance of this activity in a task;
+ * but activity can be instantiated multiple times in different tasks.
+ */
+ public static final int LAUNCH_MODE_SINGLE_INSTANCE_PER_TASK = 4;
+
+ /** Clock monotonic timestamp of launch started. */
+ public static final int START_TIMESTAMP_LAUNCH = 0;
+
+ /** Clock monotonic timestamp of finish java classloading. */
+ public static final int START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE = 1;
+
+ /** Clock monotonic timestamp of Application onCreate called. */
+ public static final int START_TIMESTAMP_APPLICATION_ONCREATE = 2;
+
+ /** Clock monotonic timestamp of bindApplication called. */
+ public static final int START_TIMESTAMP_BIND_APPLICATION = 3;
+
+ /** Clock monotonic timestamp of first frame drawn. */
+ public static final int START_TIMESTAMP_FIRST_FRAME = 4;
+
+ /** Clock monotonic timestamp of reportFullyDrawn called by application. */
+ public static final int START_TIMESTAMP_FULLY_DRAWN = 5;
+
+ /**
+ * @see #getStartupState
+ */
+ private @StartupState int mStartupState;
+
+ /**
+ * @see #getPid
+ */
+ private int mPid;
+
+ /**
+ * @see #getRealUid
+ */
+ private int mRealUid;
+
+ /**
+ * @see #getPackageUid
+ */
+ private int mPackageUid;
+
+ /**
+ * @see #getDefiningUid
+ */
+ private int mDefiningUid;
+
+ /**
+ * @see #getProcessName
+ */
+ private String mProcessName;
+
+ /**
+ * @see #getReason
+ */
+ private @StartReason int mReason;
+
+ /**
+ * @see #getStartupTimestamps
+ */
+ private Map<@StartupTimestamp Integer, Long> mStartupTimestampsNs;
+
+ /**
+ * @see #getStartType
+ */
+ private @StartType int mStartType;
+
+ /**
+ * @see #getStartIntent
+ */
+ private Intent mStartIntent;
+
+ /**
+ * @see #getLaunchMode
+ */
+ private @LaunchMode int mLaunchMode;
+
+ /**
+ * @hide *
+ */
+ @IntDef(
+ prefix = {"STARTUP_STATE_"},
+ value = {
+ STARTUP_STATE_STARTED,
+ STARTUP_STATE_ERROR,
+ STARTUP_STATE_FIRST_FRAME_DRAWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StartupState {}
+
+ /**
+ * @hide *
+ */
+ @IntDef(
+ prefix = {"START_REASON_"},
+ value = {
+ START_REASON_ALARM,
+ START_REASON_BACKUP,
+ START_REASON_BOOT_COMPLETE,
+ START_REASON_BROADCAST,
+ START_REASON_CONTENT_PROVIDER,
+ START_REASON_JOB,
+ START_REASON_LAUNCHER,
+ START_REASON_OTHER,
+ START_REASON_PUSH,
+ START_REASON_RESUMED_ACTIVITY,
+ START_REASON_SERVICE,
+ START_REASON_START_ACTIVITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StartReason {}
+
+ /**
+ * @hide *
+ */
+ @IntDef(
+ prefix = {"START_TYPE_"},
+ value = {
+ START_TYPE_COLD,
+ START_TYPE_WARM,
+ START_TYPE_HOT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StartType {}
+
+ /**
+ * @hide *
+ */
+ @IntDef(
+ prefix = {"LAUNCH_MODE_"},
+ value = {
+ LAUNCH_MODE_STANDARD,
+ LAUNCH_MODE_SINGLE_TOP,
+ LAUNCH_MODE_SINGLE_INSTANCE,
+ LAUNCH_MODE_SINGLE_TASK,
+ LAUNCH_MODE_SINGLE_INSTANCE_PER_TASK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LaunchMode {}
+
+ /**
+ * @hide *
+ */
+ @IntDef(
+ prefix = {"START_TIMESTAMP_"},
+ value = {
+ START_TIMESTAMP_LAUNCH,
+ START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE,
+ START_TIMESTAMP_APPLICATION_ONCREATE,
+ START_TIMESTAMP_BIND_APPLICATION,
+ START_TIMESTAMP_FULLY_DRAWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface StartupTimestamp {}
+
+ /**
+ * @see #getStartupState
+ * @hide
+ */
+ public void setStartupState(final @StartupState int startupState) {
+ mStartupState = startupState;
+ }
+
+ /**
+ * @see #getPid
+ * @hide
+ */
+ public void setPid(final int pid) {
+ mPid = pid;
+ }
+
+ /**
+ * @see #getRealUid
+ * @hide
+ */
+ public void setRealUid(final int uid) {
+ mRealUid = uid;
+ }
+
+ /**
+ * @see #getPackageUid
+ * @hide
+ */
+ public void setPackageUid(final int uid) {
+ mPackageUid = uid;
+ }
+
+ /**
+ * @see #getDefiningUid
+ * @hide
+ */
+ public void setDefiningUid(final int uid) {
+ mDefiningUid = uid;
+ }
+
+ /**
+ * @see #getProcessName
+ * @hide
+ */
+ public void setProcessName(final String processName) {
+ mProcessName = intern(processName);
+ }
+
+ /**
+ * @see #getReason
+ * @hide
+ */
+ public void setReason(@StartReason int reason) {
+ mReason = reason;
+ }
+
+ /**
+ * @see #getStartupTimestamps
+ * @hide
+ */
+ public void addStartupTimestamp(@StartupTimestamp int key, long timestampNs) {
+ if (mStartupTimestampsNs == null) {
+ mStartupTimestampsNs = new HashMap<@StartupTimestamp Integer, Long>();
+ }
+ mStartupTimestampsNs.put(key, timestampNs);
+ }
+
+ /**
+ * @see #getStartType
+ * @hide
+ */
+ public void setStartType(@StartType int startType) {
+ mStartType = startType;
+ }
+
+ /**
+ * @see #getStartIntent
+ * @hide
+ */
+ public void setIntent(Intent startIntent) {
+ mStartIntent = startIntent;
+ }
+
+ /**
+ * @see #getLaunchMode
+ * @hide
+ */
+ public void setLaunchMode(@LaunchMode int launchMode) {
+ mLaunchMode = launchMode;
+ }
+
+ /**
+ * Current state of startup.
+ *
+ * Can be used to determine whether the object will have additional fields added as it may be
+ * queried before all data is collected.
+ *
+ * <p class="note"> Note: field will always be set and available.</p>
+ */
+ public @StartupState int getStartupState() {
+ return mStartupState;
+ }
+
+ /**
+ * The process id.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public int getPid() {
+ return mPid;
+ }
+
+ /**
+ * The kernel user identifier of the process, most of the time the system uses this to do access
+ * control checks. It's typically the uid of the package where the component is running from,
+ * except the case of isolated process, where this field identifies the kernel user identifier
+ * that this process is actually running with, while the {@link #getPackageUid} identifies the
+ * kernel user identifier that is assigned at the package installation time.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public int getRealUid() {
+ return mRealUid;
+ }
+
+ /**
+ * Similar to {@link #getRealUid}, it's the kernel user identifier that is assigned at the
+ * package installation time.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public int getPackageUid() {
+ return mPackageUid;
+ }
+
+ /**
+ * Return the defining kernel user identifier, maybe different from {@link #getRealUid} and
+ * {@link #getPackageUid}, if an external service has the {@link
+ * android.R.styleable#AndroidManifestService_useAppZygote android:useAppZygote} set to <code>
+ * true</code> and was bound with the flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}
+ * - in this case, this field here will be the kernel user identifier of the external service
+ * provider.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public int getDefiningUid() {
+ return mDefiningUid;
+ }
+
+ /**
+ * The actual process name it was running with.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public @NonNull String getProcessName() {
+ return mProcessName;
+ }
+
+ /**
+ * The reason code of what triggered the process's start.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public @StartReason int getReason() {
+ return mReason;
+ }
+
+ /**
+ * Various clock monotonic timestamps in nanoseconds throughout the startup process.
+ *
+ * <p class="note"> Note: different timestamps will be available for different values of
+ * {@link #getStartupState}:
+ *
+ * (Subsequent rows contain all timestamps of proceding states.)
+ *
+ * For {@link #STARTUP_STATE_STARTED}, timestamp {@link #START_TIMESTAMP_LAUNCH} will be
+ * available.
+ * For {@link #STARTUP_STATE_ERROR}, no additional timestamps are guaranteed available.
+ * For {@link #STARTUP_STATE_FIRST_FRAME_DRAWN}, timestamps
+ * {@link #START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE}, {@link #START_TIMESTAMP_APPLICATION_ONCREATE},
+ * {@link #START_TIMESTAMP_BIND_APPLICATION}, and {@link #START_TIMESTAMP_FIRST_FRAME} will
+ * additionally be available.
+ *
+ * Timestamp {@link #START_TIMESTAMP_FULLY_DRAWN} is never guaranteed to be available as it is
+ * dependant on devloper calling {@link Activity#reportFullyDrawn}.
+ * </p>
+ */
+ public @NonNull Map<@StartupTimestamp Integer, Long> getStartupTimestamps() {
+ if (mStartupTimestampsNs == null) {
+ mStartupTimestampsNs = new HashMap<@StartupTimestamp Integer, Long>();
+ }
+ return mStartupTimestampsNs;
+ }
+
+ /**
+ * The state of the app at startup.
+ *
+ * <p class="note"> Note: field will be set for {@link #getStartupState} value
+ * {@link #STARTUP_STATE_FIRST_FRAME_DRAWN}. Not guaranteed for other states.</p>
+ */
+ public @StartType int getStartType() {
+ return mStartType;
+ }
+
+ /**
+ * The intent used to launch the application.
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ @SuppressLint("IntentBuilderName")
+ @Nullable
+ public Intent getIntent() {
+ return mStartIntent;
+ }
+
+ /**
+ * An instruction on how the activity should be launched. There are five modes that work in
+ * conjunction with activity flags in Intent objects to determine what should happen when the
+ * activity is called upon to handle an intent.
+ *
+ * Modes:
+ * {@link #LAUNCH_MODE_STANDARD}
+ * {@link #LAUNCH_MODE_SINGLE_TOP}
+ * {@link #LAUNCH_MODE_SINGLE_INSTANCE}
+ * {@link #LAUNCH_MODE_SINGLE_TASK}
+ * {@link #LAUNCH_MODE_SINGLE_INSTANCE_PER_TASK}
+ *
+ * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
+ */
+ public @LaunchMode int getLaunchMode() {
+ return mLaunchMode;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStartupState);
+ dest.writeInt(mPid);
+ dest.writeInt(mRealUid);
+ dest.writeInt(mPackageUid);
+ dest.writeInt(mDefiningUid);
+ dest.writeString(mProcessName);
+ dest.writeInt(mReason);
+ dest.writeInt(mStartupTimestampsNs.size());
+ for (@StartupTimestamp int key : mStartupTimestampsNs.keySet()) {
+ dest.writeInt(key);
+ dest.writeLong(mStartupTimestampsNs.get(key));
+ }
+ dest.writeInt(mStartType);
+ dest.writeParcelable(mStartIntent, flags);
+ dest.writeInt(mLaunchMode);
+ }
+
+ /** @hide */
+ public ApplicationStartInfo() {}
+
+ /** @hide */
+ public ApplicationStartInfo(ApplicationStartInfo other) {
+ mStartupState = other.mStartupState;
+ mPid = other.mPid;
+ mRealUid = other.mRealUid;
+ mPackageUid = other.mPackageUid;
+ mDefiningUid = other.mDefiningUid;
+ mProcessName = other.mProcessName;
+ mReason = other.mReason;
+ mStartupTimestampsNs = other.mStartupTimestampsNs;
+ mStartType = other.mStartType;
+ mStartIntent = other.mStartIntent;
+ mLaunchMode = other.mLaunchMode;
+ }
+
+ private ApplicationStartInfo(@NonNull Parcel in) {
+ mStartupState = in.readInt();
+ mPid = in.readInt();
+ mRealUid = in.readInt();
+ mPackageUid = in.readInt();
+ mDefiningUid = in.readInt();
+ mProcessName = intern(in.readString());
+ mReason = in.readInt();
+ int starupTimestampCount = in.readInt();
+ for (int i = 0; i < starupTimestampCount; i++) {
+ int key = in.readInt();
+ long val = in.readLong();
+ addStartupTimestamp(key, val);
+ }
+ mStartType = in.readInt();
+ mStartIntent =
+ in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
+ mLaunchMode = in.readInt();
+ }
+
+ private static String intern(@Nullable String source) {
+ return source != null ? source.intern() : null;
+ }
+
+ public @NonNull static final Creator<ApplicationStartInfo> CREATOR =
+ new Creator<ApplicationStartInfo>() {
+ @Override
+ public ApplicationStartInfo createFromParcel(Parcel in) {
+ return new ApplicationStartInfo(in);
+ }
+
+ @Override
+ public ApplicationStartInfo[] newArray(int size) {
+ return new ApplicationStartInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 03646c6..5e40399 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -121,6 +121,9 @@
oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
+ oneway void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim,
+ int backgroundColor);
+ oneway void clearOverrideActivityTransition(IBinder token, boolean open);
/**
* Overrides the animation of activity pending transition. This call is not one-way because
* the method is usually used after startActivity or Activity#finish. If this is non-blocking,
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index c5c3d30..29d80f8 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -19,10 +19,12 @@
import android.app.ActivityManager;
import android.app.ActivityManager.PendingIntentInfo;
import android.app.ActivityTaskManager;
+import android.app.ApplicationStartInfo;
import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
import android.app.ContentProviderHolder;
import android.app.GrantedUriPermission;
+import android.app.IApplicationStartInfoCompleteListener;
import android.app.IApplicationThread;
import android.app.IActivityController;
import android.app.IAppTask;
@@ -93,7 +95,7 @@
// below block of transactions.
// Since these transactions are also called from native code, these must be kept in sync with
- // the ones in frameworks/native/libs/binder/include/binder/IActivityManager.h
+ // the ones in frameworks/native/libs/binder/include_activitymanager/binder/ActivityManager.h
// =============== Beginning of transactions used on native side as well ======================
ParcelFileDescriptor openContentUri(in String uriString);
void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
@@ -485,8 +487,18 @@
// Start of L transactions
String getTagForIntentSender(in IIntentSender sender, in String prefix);
+
+ /**
+ * Starts a user in the background (i.e., while another user is running in the foreground).
+ *
+ * Notice that a background user is "invisible" and cannot launch activities. Starting on
+ * Android U, all users started with this method are invisible, even profiles (prior to Android
+ * U, profiles started with this method would be visible if its parent was the current user) -
+ * if you want to start a profile visible, you should call {@code startProfile()} instead.
+ */
@UnsupportedAppUsage
boolean startUserInBackground(int userid);
+
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isInLockTaskMode();
@UnsupportedAppUsage
@@ -634,6 +646,45 @@
void appNotResponding(String reason);
/**
+ * Return a list of {@link ApplicationStartInfo} records.
+ *
+ * <p class="note"> Note: System stores historical information in a ring buffer, older
+ * records would be overwritten by newer records. </p>
+ *
+ * @param packageName Optional, an empty value means match all packages belonging to the
+ * caller's UID. If this package belongs to another UID, you must hold
+ * {@link android.Manifest.permission#DUMP} in order to retrieve it.
+ * @param maxNum Optional, the maximum number of results should be returned; A value of 0
+ * means to ignore this parameter and return all matching records
+ * @param userId The userId in the multi-user environment.
+ *
+ * @return a list of {@link ApplicationStartInfo} records with the matching criteria, sorted in
+ * the order from most recent to least recent.
+ */
+ ParceledListSlice<ApplicationStartInfo> getHistoricalProcessStartReasons(String packageName,
+ int maxNum, int userId);
+
+
+ /**
+ * Sets a callback for {@link ApplicationStartInfo} upon completion of collecting startup data.
+ *
+ * <p class="note"> Note: completion of startup is no guaranteed and as such this callback may not occur.</p>
+ *
+ * @param listener A listener to for the callback upon completion of startup data collection.
+ * @param userId The userId in the multi-user environment.
+ */
+ void setApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
+ int userId);
+
+
+ /**
+ * Removes callback for {@link ApplicationStartInfo} upon completion of collecting startup data.
+ *
+ * @param userId The userId in the multi-user environment.
+ */
+ void removeApplicationStartInfoCompleteListener(int userId);
+
+ /**
* Return a list of {@link ApplicationExitInfo} records.
*
* <p class="note"> Note: System stores these historical information in a ring buffer, older
diff --git a/core/java/android/app/IApplicationStartInfoCompleteListener.aidl b/core/java/android/app/IApplicationStartInfoCompleteListener.aidl
new file mode 100644
index 0000000..0f0d919
--- /dev/null
+++ b/core/java/android/app/IApplicationStartInfoCompleteListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.app.ApplicationStartInfo;
+
+/**
+ * Notify the client when compilation of app start info is complete.
+ * Will not be called in case of an error that disrupts startup.
+ * @param applicationStartInfo the completed ApplicationStartInfo object.
+ *
+ * @hide
+ */
+interface IApplicationStartInfoCompleteListener {
+ void onApplicationStartInfoComplete(in ApplicationStartInfo applicationStartInfo);
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 302d146..ab32f4d 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -80,8 +80,6 @@
boolean isImportanceLocked(String pkg, int uid);
List<String> getAllowedAssistantAdjustments(String pkg);
- void allowAssistantAdjustment(String adjustmentType);
- void disallowAssistantAdjustment(String adjustmentType);
boolean shouldHideSilentStatusIcons(String callingPkg);
void setHideSilentStatusIcons(boolean hide);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9974e29..7ab65b1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4624,16 +4624,15 @@
/**
* Set whether this is an "ongoing" notification.
*
-
- * Ongoing notifications cannot be dismissed by the user, so your application or service
- * must take care of canceling them.
+ * Ongoing notifications cannot be dismissed by the user on locked devices, or by
+ * notification listeners, and some notifications cannnot be dismissed on unlocked
+ * devices (system, device management, media), so your application or service must take
+ * care of canceling them.
*
-
* They are typically used to indicate a background task that the user is actively engaged
* with (e.g., playing music) or is pending in some way and therefore occupying the device
* (e.g., a file download, sync operation, active network connection).
*
-
* @see Notification#FLAG_ONGOING_EVENT
*/
@NonNull
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 4e94a1d..82adaaf 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1577,32 +1577,6 @@
}
}
- /**
- * @hide
- */
- @TestApi
- public void allowAssistantAdjustment(String capability) {
- INotificationManager service = getService();
- try {
- service.allowAssistantAdjustment(capability);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- */
- @TestApi
- public void disallowAssistantAdjustment(String capability) {
- INotificationManager service = getService();
- try {
- service.disallowAssistantAdjustment(capability);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
/** @hide */
@TestApi
public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String pkg) {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 824c7cc..e72b141 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -4,7 +4,7 @@
# ActivityManager
per-file ActivityManager* = file:/services/core/java/com/android/server/am/OWNERS
-per-file ApplicationStartInfo* = file:/services/core/java/com/android/server/am/OWNERS
+per-file *ApplicationStartInfo* = file:/services/core/java/com/android/server/am/OWNERS
per-file ApplicationErrorReport* = file:/services/core/java/com/android/server/am/OWNERS
per-file ApplicationExitInfo* = file:/services/core/java/com/android/server/am/OWNERS
per-file Application.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 64538ec..6d80a44 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1570,7 +1570,7 @@
new CachedServiceFetcher<SharedConnectivityManager>() {
@Override
public SharedConnectivityManager createService(ContextImpl ctx) {
- return new SharedConnectivityManager(ctx);
+ return SharedConnectivityManager.create(ctx);
}
});
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index f133c8a..0bdc222 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -218,6 +218,23 @@
"file_patterns": [
"(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*"
]
+ },
+ {
+ "name": "HdmiCecTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.hardware.hdmi"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*"
+ ]
}
],
"presubmit-large": [
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
new file mode 100644
index 0000000..14494d7
--- /dev/null
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_ACCOUNT_TYPE;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a policy that relates to a certain account type
+ * (e.g. {@link DevicePolicyManager#setAccountManagementDisabled}).
+ *
+ * @hide
+ */
+@SystemApi
+public final class AccountTypePolicyKey extends PolicyKey {
+ private static final String ATTR_ACCOUNT_TYPE = "account-type";
+
+ private final String mAccountType;
+
+ /**
+ * @hide
+ */
+ public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
+ super(key);
+ mAccountType = Objects.requireNonNull((accountType));
+ }
+
+ private AccountTypePolicyKey(Parcel source) {
+ super(source.readString());
+ mAccountType = source.readString();
+ }
+
+ /**
+ * @hide
+ */
+ public AccountTypePolicyKey(String key) {
+ super(key);
+ mAccountType = null;
+ }
+
+ /**
+ * Returns the account type this policy relates to.
+ */
+ @NonNull
+ public String getAccountType() {
+ return mAccountType;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+ serializer.attribute(/* namespace= */ null, ATTR_ACCOUNT_TYPE, mAccountType);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public AccountTypePolicyKey readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String policyKey = parser.getAttributeValue(/* namespace= */ null,
+ ATTR_POLICY_IDENTIFIER);
+ String accountType = parser.getAttributeValue(/* namespace= */ null, ATTR_ACCOUNT_TYPE);
+ return new AccountTypePolicyKey(policyKey, accountType);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToBundle(Bundle bundle) {
+ bundle.putString(EXTRA_POLICY_KEY, getIdentifier());
+ Bundle extraPolicyParams = new Bundle();
+ extraPolicyParams.putString(EXTRA_ACCOUNT_TYPE, mAccountType);
+ bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AccountTypePolicyKey other = (AccountTypePolicyKey) o;
+ return Objects.equals(getIdentifier(), other.getIdentifier())
+ && Objects.equals(mAccountType, other.mAccountType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getIdentifier(), mAccountType);
+ }
+
+ @Override
+ public String toString() {
+ return "AccountTypePolicyKey{mPolicyKey= " + getIdentifier()
+ + "; mAccountType= " + mAccountType + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getIdentifier());
+ dest.writeString(mAccountType);
+ }
+
+ @NonNull
+ public static final Creator<AccountTypePolicyKey> CREATOR =
+ new Creator<AccountTypePolicyKey>() {
+ @Override
+ public AccountTypePolicyKey createFromParcel(Parcel source) {
+ return new AccountTypePolicyKey(source);
+ }
+
+ @Override
+ public AccountTypePolicyKey[] newArray(int size) {
+ return new AccountTypePolicyKey[size];
+ }
+ };
+}
diff --git a/core/java/android/app/admin/DeviceAdminAuthority.java b/core/java/android/app/admin/DeviceAdminAuthority.java
index 5d1ff11..d363147 100644
--- a/core/java/android/app/admin/DeviceAdminAuthority.java
+++ b/core/java/android/app/admin/DeviceAdminAuthority.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
/**
@@ -31,11 +32,18 @@
public final class DeviceAdminAuthority extends Authority {
/**
+ * Object representing a device admin authority.
+ *
* @hide
*/
+ @TestApi
+ @NonNull
public static final DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY = new DeviceAdminAuthority();
- private DeviceAdminAuthority() {}
+ /**
+ * Creates an authority that represents a device admin.
+ */
+ public DeviceAdminAuthority() {}
@Override
public String toString() {
@@ -44,12 +52,13 @@
@Override
public boolean equals(@Nullable Object o) {
- return super.equals(o);
+ if (this == o) return true;
+ return o != null && getClass() == o.getClass();
}
@Override
public int hashCode() {
- return super.hashCode();
+ return 0;
}
@Override
@@ -65,7 +74,7 @@
new Creator<DeviceAdminAuthority>() {
@Override
public DeviceAdminAuthority createFromParcel(Parcel source) {
- return new DeviceAdminAuthority();
+ return DEVICE_ADMIN_AUTHORITY;
}
@Override
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
new file mode 100644
index 0000000..f6b070a
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.os.UserManager;
+
+import java.util.Objects;
+
+/**
+ * Class containing identifiers for policy APIs in {@link DevicePolicyManager}, for example they
+ * will be passed in {@link PolicyUpdatesReceiver#onPolicySetResult} and
+ * {@link PolicyUpdatesReceiver#onPolicyChanged} to communicate updates of a certain policy back
+ * to the admin.
+ */
+public final class DevicePolicyIdentifiers {
+
+ private DevicePolicyIdentifiers() {}
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setAutoTimeZoneEnabled}.
+ */
+ public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setPermissionGrantState}.
+ */
+ public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setLockTaskPackages}.
+ */
+ public static final String LOCK_TASK_POLICY = "lockTask";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setUserControlDisabledPackages}.
+ */
+ public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY =
+ "userControlDisabledPackages";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#addPersistentPreferredActivity}.
+ */
+ public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY =
+ "persistentPreferredActivity";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setUninstallBlocked}.
+ */
+ public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setApplicationRestrictions}.
+ */
+ public static final String APPLICATION_RESTRICTIONS_POLICY = "applicationRestrictions";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setResetPasswordToken}.
+ */
+ public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setAccountManagementDisabled}.
+ */
+ public static final String ACCOUNT_MANAGEMENT_DISABLED_POLICY = "accountManagementDisabled";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setApplicationHidden}.
+ */
+ public static final String APPLICATION_HIDDEN_POLICY = "applicationHidden";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setCameraDisabled}.
+ */
+ public static final String CAMERA_DISABLED_POLICY = "cameraDisabled";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setStatusBarDisabled}.
+ */
+ public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setPackagesSuspended}.
+ */
+ public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setKeyguardDisabledFeatures}.
+ */
+ public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setAutoTimeEnabled}.
+ */
+ public static final String AUTO_TIME_POLICY = "autoTime";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setBackupServiceEnabled}.
+ */
+ public static final String BACKUP_SERVICE_POLICY = "backupService";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setPermittedInputMethods}.
+ *
+ * @hide
+ */
+ public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setPersonalAppsSuspended}.
+ *
+ * @hide
+ */
+ public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setScreenCaptureDisabled}.
+ *
+ * @hide
+ */
+ public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#setTrustAgentConfiguration}.
+ *
+ * @hide
+ */
+ public static final String TRUST_AGENT_CONFIGURATION_POLICY = "trustAgentConfiguration";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#addCrossProfileIntentFilter}.
+ *
+ * @hide
+ */
+ public static final String CROSS_PROFILE_INTENT_FILTER_POLICY = "crossProfileIntentFilter";
+
+ /**
+ * String identifier for {@link DevicePolicyManager#addCrossProfileWidgetProvider}.
+ *
+ * @hide
+ */
+ public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider";
+
+ /**
+ * @hide
+ */
+ public static final String USER_RESTRICTION_PREFIX = "userRestriction_";
+
+ /**
+ * Returns a string identifier for the provided user restrictions, see
+ * {@link DevicePolicyManager#addUserRestriction} and {@link UserManager} for the list of
+ * available restrictions.
+ */
+ @NonNull
+ public static String getIdentifierForUserRestriction(
+ @UserManager.UserRestrictionKey @NonNull String restriction) {
+ Objects.requireNonNull(restriction);
+ return USER_RESTRICTION_PREFIX + restriction;
+ }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e7f6990..4016e32 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.SET_TIME;
@@ -3896,6 +3897,23 @@
public static final int EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = 2;
/**
+ * Prevent an app from entering hibernation.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EXEMPT_FROM_HIBERNATION = 3;
+
+ /**
+ * Exempt an app from the start foreground service from background with while in user permission
+ * restriction.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION = 4;
+
+ /**
* Exemptions to platform restrictions, given to an application through
* {@link #setApplicationExemptions(String, Set)}.
*
@@ -3904,7 +3922,9 @@
@IntDef(prefix = { "EXEMPT_FROM_"}, value = {
EXEMPT_FROM_APP_STANDBY,
EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
- EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION
+ EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+ EXEMPT_FROM_HIBERNATION,
+ EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationExemptionConstants {}
@@ -4020,52 +4040,6 @@
return MTE_NOT_CONTROLLED_BY_POLICY;
}
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
-
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
-
-
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String LOCK_TASK_POLICY = "lockTask";
-
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY =
- "userControlDisabledPackages";
-
-
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY =
- "persistentPreferredActivity";
-
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
-
- // TODO: Expose this as SystemAPI once we add the query API
- /**
- * @hide
- */
- public static final String APPLICATION_RESTRICTIONS_POLICY = "applicationRestrictions";
-
/**
* This object is a single place to tack on invalidation and disable calls. All
* binder caches in this class derive from this Config, so all can be invalidated or
@@ -6885,6 +6859,11 @@
public static final int KEYGUARD_DISABLE_IRIS = 1 << 8;
/**
+ * Disable all keyguard shortcuts.
+ */
+ public static final int KEYGUARD_DISABLE_SHORTCUTS_ALL = 1 << 9;
+
+ /**
* NOTE: Please remember to update the DevicePolicyManagerTest's testKeyguardDisabledFeatures
* CTS test when adding to the list above.
*/
@@ -6927,7 +6906,8 @@
*/
public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
- | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+ | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
+ | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL;
/**
* Keyguard features that when set on a normal or organization-owned managed profile, have
@@ -8265,15 +8245,18 @@
* legacy device admins targeting SDK version {@link android.os.Build.VERSION_CODES#P} or
* below will be silently ignored.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with or null if
+ the caller is not a device admin
* @param disabled Whether or not the camera should be disabled.
* @throws SecurityException if {@code admin} is not an active administrator or does not use
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA}.
*/
- public void setCameraDisabled(@NonNull ComponentName admin, boolean disabled) {
+ @RequiresPermission(value = MANAGE_DEVICE_POLICY_CAMERA, conditional = true)
+ public void setCameraDisabled(@Nullable ComponentName admin, boolean disabled) {
if (mService != null) {
try {
- mService.setCameraDisabled(admin, disabled, mParentInstance);
+ mService.setCameraDisabled(admin, mContext.getPackageName(), disabled,
+ mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8744,7 +8727,8 @@
* {@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS},
* {@link #KEYGUARD_DISABLE_FINGERPRINT},
* {@link #KEYGUARD_DISABLE_FACE},
- * {@link #KEYGUARD_DISABLE_IRIS}.
+ * {@link #KEYGUARD_DISABLE_IRIS},
+ * {@link #KEYGUARD_DISABLE_SHORTCUTS_ALL}.
* @throws SecurityException if {@code admin} is not an active administrator or does not user
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
*/
@@ -11285,6 +11269,13 @@
* See the constants in {@link android.os.UserManager} for the list of restrictions that can
* be enforced device-wide.
*
+ * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or
+ * above, calling this API will result in applying the restriction locally on the calling user,
+ * or locally on the parent profile if called from the
+ * {@link DevicePolicyManager} instance obtained from
+ * {@link #getParentProfileInstance(ComponentName)}. To set a restriction globally, call
+ * {@link #addUserRestrictionGlobally} instead.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner.
@@ -11293,7 +11284,38 @@
@UserManager.UserRestrictionKey String key) {
if (mService != null) {
try {
- mService.setUserRestriction(admin, key, true, mParentInstance);
+ mService.setUserRestriction(
+ admin, mContext.getPackageName(), key, true, mParentInstance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by a profile or device owner to set a user restriction specified by the provided
+ * {@code key} globally on all users. To clear the restriction use
+ * {@link #clearUserRestriction}.
+ *
+ * <p>For a given user, a restriction will be set if it was applied globally or locally by any
+ * admin.
+ *
+ * <p> The calling device admin must be a profile or device owner; if it is not, a security
+ * exception will be thrown.
+ *
+ * <p> See the constants in {@link android.os.UserManager} for the list of restrictions that can
+ * be enforced device-wide.
+ *
+ * @param key The key of the restriction.
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws IllegalStateException if caller is not targeting Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above.
+ */
+ public void addUserRestrictionGlobally(@NonNull @UserManager.UserRestrictionKey String key) {
+ throwIfParentInstance("addUserRestrictionGlobally");
+ if (mService != null) {
+ try {
+ mService.setUserRestrictionGlobally(mContext.getPackageName(), key);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -11312,6 +11334,10 @@
* <p>
* See the constants in {@link android.os.UserManager} for the list of restrictions.
*
+ * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or
+ * above, calling this API will result in clearing any local and global restriction with the
+ * specified key that was previously set by the caller.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner.
@@ -11320,7 +11346,8 @@
@UserManager.UserRestrictionKey String key) {
if (mService != null) {
try {
- mService.setUserRestriction(admin, key, false, mParentInstance);
+ mService.setUserRestriction(
+ admin, mContext.getPackageName(), key, false, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -11340,6 +11367,18 @@
* {@link #getParentProfileInstance(ComponentName)}, for retrieving device-wide restrictions
* it previously set with {@link #addUserRestriction(ComponentName, String)}.
*
+ * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or
+ * above, this API will return the local restrictions set on the calling user, or on the parent
+ * profile if called from the {@link DevicePolicyManager} instance obtained from
+ * {@link #getParentProfileInstance(ComponentName)}. To get global restrictions set by admin,
+ * call {@link #getUserRestrictionsGlobally()} instead.
+ *
+ * <p>Note that this is different that the returned restrictions for callers targeting pre
+ * Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, were this API returns
+ * all local/global restrictions set by the admin on the calling user using
+ * {@link #addUserRestriction(ComponentName, String)} or the parent user if called on the
+ * {@link DevicePolicyManager} instance it obtained from {@link #getParentProfileInstance}.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return a {@link Bundle} whose keys are the user restrictions, and the values a
* {@code boolean} indicating whether the restriction is set.
@@ -11349,7 +11388,33 @@
Bundle ret = null;
if (mService != null) {
try {
- ret = mService.getUserRestrictions(admin, mParentInstance);
+ ret = mService.getUserRestrictions(
+ admin, mContext.getPackageName(), mParentInstance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return ret == null ? new Bundle() : ret;
+ }
+
+ /**
+ * Called by a profile or device owner to get global user restrictions set with
+ * {@link #addUserRestrictionGlobally(String)}.
+ * <p>
+ * To get all the user restrictions currently set for a certain user, use
+ * {@link UserManager#getUserRestrictions()}.
+ * @return a {@link Bundle} whose keys are the user restrictions, and the values a
+ * {@code boolean} indicating whether the restriction is set.
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws IllegalStateException if caller is not targeting Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above.
+ */
+ public @NonNull Bundle getUserRestrictionsGlobally() {
+ throwIfParentInstance("createAdminSupportIntent");
+ Bundle ret = null;
+ if (mService != null) {
+ try {
+ ret = mService.getUserRestrictionsGlobally(mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -12571,6 +12636,33 @@
}
/**
+ * Returns whether the status bar is disabled/enabled, see {@link #setStatusBarDisabled}.
+ *
+ * <p>Callable by device owner or profile owner of secondary users that is affiliated with the
+ * device owner.
+ *
+ * <p>This policy has no effect in LockTask mode. The behavior of the
+ * status bar in LockTask mode can be configured with
+ * {@link #setLockTaskFeatures(ComponentName, int)}.
+ *
+ * <p>This policy also does not have any effect while on the lock screen, where the status bar
+ * will not be disabled.
+ *
+ * @throws SecurityException if the caller is not the device owner, or a profile owner of
+ * secondary user that is affiliated with the device.
+ * @see #isAffiliatedUser
+ * @see #getSecondaryUsers
+ */
+ public boolean isStatusBarDisabled() {
+ throwIfParentInstance("isStatusBarDisabled");
+ try {
+ return mService.isStatusBarDisabled(mContext.getPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called by the system update service to notify device and profile owners of pending system
* updates.
*
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 11b840f..a498913 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1836,6 +1836,27 @@
*/
public static final String WORK_PROFILE_BADGED_LABEL =
PREFIX + "WORK_PROFILE_BADGED_LABEL";
+
+ /**
+ * Notification title. This notification lets the user know that they will be unable to
+ * receive phone calls or texts until the work profile is turned on.
+ */
+ public static final String WORK_PROFILE_TELEPHONY_PAUSED_TITLE =
+ PREFIX + "WORK_PROFILE_TELEPHONY_UNAVAILABLE_TITLE";
+
+ /**
+ * Notification text. This notification lets the user know that they will be unable to
+ * receive phone calls or texts until the work profile is turned on.
+ */
+ public static final String WORK_PROFILE_TELEPHONY_PAUSED_BODY =
+ PREFIX + "WORK_PROFILE_TELEPHONY_UNAVAILABLE_BODY";
+
+ /**
+ * Label for notification button. This button lets the user turn the work profile on.
+ */
+ public static final String WORK_PROFILE_TELEPHONY_PAUSED_TURN_ON_BUTTON =
+ PREFIX + "TURN_ON_WORK_PROFILE_BUTTON_TEXT";
+
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyState.java b/core/java/android/app/admin/DevicePolicyState.java
index ee33b00..6de5150 100644
--- a/core/java/android/app/admin/DevicePolicyState.java
+++ b/core/java/android/app/admin/DevicePolicyState.java
@@ -82,6 +82,11 @@
}
@Override
+ public String toString() {
+ return "DevicePolicyState { mPolicies= " + mPolicies + " }";
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/app/admin/DpcAuthority.java b/core/java/android/app/admin/DpcAuthority.java
index 72c16bc..cd9da9a 100644
--- a/core/java/android/app/admin/DpcAuthority.java
+++ b/core/java/android/app/admin/DpcAuthority.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
/**
@@ -31,11 +32,18 @@
public final class DpcAuthority extends Authority {
/**
+ * Object representing a DPC authority.
+ *
* @hide
*/
+ @NonNull
+ @TestApi
public static final DpcAuthority DPC_AUTHORITY = new DpcAuthority();
- private DpcAuthority() {}
+ /**
+ * Creates an authority that represents a DPC admin.
+ */
+ public DpcAuthority() {}
@Override
public String toString() {
@@ -44,12 +52,13 @@
@Override
public boolean equals(@Nullable Object o) {
- return super.equals(o);
+ if (this == o) return true;
+ return o != null && getClass() == o.getClass();
}
@Override
public int hashCode() {
- return super.hashCode();
+ return 0;
}
@Override
@@ -65,7 +74,7 @@
new Creator<DpcAuthority>() {
@Override
public DpcAuthority createFromParcel(Parcel source) {
- return new DpcAuthority();
+ return DPC_AUTHORITY;
}
@Override
diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java
index 1786467..771794d 100644
--- a/core/java/android/app/admin/EnforcingAdmin.java
+++ b/core/java/android/app/admin/EnforcingAdmin.java
@@ -38,7 +38,7 @@
private final UserHandle mUserHandle;
/**
- * @hide
+ * Creates an enforcing admin with the given params.
*/
public EnforcingAdmin(
@NonNull String packageName, @NonNull Authority authority,
diff --git a/core/java/android/app/admin/FlagUnion.java b/core/java/android/app/admin/FlagUnion.java
index be924d8..599373f 100644
--- a/core/java/android/app/admin/FlagUnion.java
+++ b/core/java/android/app/admin/FlagUnion.java
@@ -22,8 +22,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Objects;
-
/**
* Class to identify a union resolution mechanism for flag policies, it's used to resolve the
* enforced policy when being set by multiple admins (see
@@ -35,8 +33,10 @@
public final class FlagUnion extends ResolutionMechanism<Integer> {
/**
- * @hide
+ * Union resolution for policies represented as int flags which resolves as the union of all
+ * flags.
*/
+ @NonNull
public static final FlagUnion FLAG_UNION = new FlagUnion();
private FlagUnion(){};
@@ -49,7 +49,7 @@
@Override
public int hashCode() {
- return Objects.hash(this);
+ return 0;
}
@Override
@@ -70,7 +70,7 @@
new Parcelable.Creator<FlagUnion>() {
@Override
public FlagUnion createFromParcel(Parcel source) {
- return new FlagUnion();
+ return FLAG_UNION;
}
@Override
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c86852a..5b9e948 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -141,7 +141,7 @@
boolean requestBugreport(in ComponentName who);
- void setCameraDisabled(in ComponentName who, boolean disabled, boolean parent);
+ void setCameraDisabled(in ComponentName who, String callerPackageName, boolean disabled, boolean parent);
boolean getCameraDisabled(in ComponentName who, int userHandle, boolean parent);
void setScreenCaptureDisabled(in ComponentName who, boolean disabled, boolean parent);
@@ -247,8 +247,11 @@
void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
ComponentName getRestrictionsProvider(int userHandle);
- void setUserRestriction(in ComponentName who, in String key, boolean enable, boolean parent);
- Bundle getUserRestrictions(in ComponentName who, boolean parent);
+ void setUserRestriction(in ComponentName who, in String callerPackage, in String key, boolean enable, boolean parent);
+ void setUserRestrictionGlobally(in String callerPackage, in String key);
+ Bundle getUserRestrictions(in ComponentName who, in String callerPackage, boolean parent);
+ Bundle getUserRestrictionsGlobally(in String callerPackage);
+
void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
void clearCrossProfileIntentFilters(in ComponentName admin);
@@ -379,6 +382,7 @@
boolean setKeyguardDisabled(in ComponentName admin, boolean disabled);
boolean setStatusBarDisabled(in ComponentName who, boolean disabled);
+ boolean isStatusBarDisabled(in String callerPackage);
boolean getDoNotAskCredentialsOnBoot();
void notifyPendingSystemUpdate(in SystemUpdateInfo info);
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index d7eb101..a49152d 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -18,6 +18,7 @@
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_INTENT_FILTER;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -100,7 +101,7 @@
*/
@Override
public void writeToBundle(Bundle bundle) {
- super.writeToBundle(bundle);
+ bundle.putString(EXTRA_POLICY_KEY, getIdentifier());
Bundle extraPolicyParams = new Bundle();
extraPolicyParams.putParcelable(EXTRA_INTENT_FILTER, mFilter);
bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
@@ -117,7 +118,7 @@
@Override
public int hashCode() {
- return Objects.hash(getIdentifier(), mFilter);
+ return Objects.hash(getIdentifier());
}
@Override
@@ -133,7 +134,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(getIdentifier());
- mFilter.writeToParcel(dest, flags);
+ dest.writeTypedObject(mFilter, flags);
}
@NonNull
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index 28757df..f5d1cb4 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -22,7 +22,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -67,7 +66,7 @@
@Override
@NonNull
public LockTaskPolicy getValue() {
- return super.getValue();
+ return this;
}
/**
@@ -76,6 +75,7 @@
public LockTaskPolicy(@NonNull Set<String> packages) {
Objects.requireNonNull(packages);
mPackages.addAll(packages);
+ setValue(this);
}
/**
@@ -89,9 +89,13 @@
}
private LockTaskPolicy(Parcel source) {
- String[] packages = Objects.requireNonNull(source.readStringArray());
- mPackages = new HashSet<>(Arrays.stream(packages).toList());
+ int size = source.readInt();
+ mPackages = new HashSet<>();
+ for (int i = 0; i < size; i++) {
+ mPackages.add(source.readString());
+ }
mFlags = source.readInt();
+ setValue(this);
}
/**
@@ -100,6 +104,7 @@
public LockTaskPolicy(LockTaskPolicy policy) {
mPackages = new HashSet<>(policy.mPackages);
mFlags = policy.mFlags;
+ setValue(this);
}
/**
@@ -144,7 +149,10 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeArray(mPackages.toArray(new String[0]));
+ dest.writeInt(mPackages.size());
+ for (String p : mPackages) {
+ dest.writeString(p);
+ }
dest.writeInt(mFlags);
}
diff --git a/core/java/android/app/admin/LongPolicyValue.java b/core/java/android/app/admin/LongPolicyValue.java
new file mode 100644
index 0000000..b149b8a
--- /dev/null
+++ b/core/java/android/app/admin/LongPolicyValue.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class LongPolicyValue extends PolicyValue<Long> {
+
+ public LongPolicyValue(long value) {
+ super(value);
+ }
+
+ private LongPolicyValue(Parcel source) {
+ this(source.readLong());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LongPolicyValue other = (LongPolicyValue) o;
+ return Objects.equals(getValue(), other.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getValue());
+ }
+
+ @Override
+ public String toString() {
+ return "LongPolicyValue { mValue= " + getValue() + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(getValue());
+ }
+
+ @NonNull
+ public static final Creator<LongPolicyValue> CREATOR =
+ new Creator<LongPolicyValue>() {
+ @Override
+ public LongPolicyValue createFromParcel(Parcel source) {
+ return new LongPolicyValue(source);
+ }
+
+ @Override
+ public LongPolicyValue[] newArray(int size) {
+ return new LongPolicyValue[size];
+ }
+ };
+}
diff --git a/core/java/android/app/admin/MostRecent.java b/core/java/android/app/admin/MostRecent.java
index ac1657189..9df4b53 100644
--- a/core/java/android/app/admin/MostRecent.java
+++ b/core/java/android/app/admin/MostRecent.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +33,23 @@
@TestApi
public final class MostRecent<V> extends ResolutionMechanism<V> {
+ /**
+ * Indicates that the most recent setter of the policy wins the resolution.
+ */
+ @NonNull
+ public static final MostRecent<?> MOST_RECENT = new MostRecent<>();
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ return o != null && getClass() == o.getClass();
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
@Override
public String toString() {
return "MostRecent {}";
@@ -46,15 +64,15 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {}
@NonNull
- public static final Parcelable.Creator<MostRecent> CREATOR =
- new Parcelable.Creator<MostRecent>() {
+ public static final Parcelable.Creator<MostRecent<?>> CREATOR =
+ new Parcelable.Creator<MostRecent<?>>() {
@Override
- public MostRecent createFromParcel(Parcel source) {
- return new MostRecent();
+ public MostRecent<?> createFromParcel(Parcel source) {
+ return new MostRecent<>();
}
@Override
- public MostRecent[] newArray(int size) {
+ public MostRecent<?>[] newArray(int size) {
return new MostRecent[size];
}
};
diff --git a/core/java/android/app/admin/MostRestrictive.java b/core/java/android/app/admin/MostRestrictive.java
index adb4744..bbe6eb2 100644
--- a/core/java/android/app/admin/MostRestrictive.java
+++ b/core/java/android/app/admin/MostRestrictive.java
@@ -47,7 +47,8 @@
/**
* Returns an ordered list of most to least restrictive values for a certain policy.
*/
- List<V> getMostToLeastRestrictiveValues() {
+ @NonNull
+ public List<V> getMostToLeastRestrictiveValues() {
return mMostToLeastRestrictive.stream().map(PolicyValue::getValue).toList();
}
@@ -55,13 +56,17 @@
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- MostRestrictive other = (MostRestrictive) o;
- return Objects.equals(mMostToLeastRestrictive, other.mMostToLeastRestrictive);
+ try {
+ MostRestrictive<V> other = (MostRestrictive<V>) o;
+ return Objects.equals(mMostToLeastRestrictive, other.mMostToLeastRestrictive);
+ } catch (ClassCastException exception) {
+ return false;
+ }
}
@Override
public int hashCode() {
- return Objects.hash(mMostToLeastRestrictive);
+ return mMostToLeastRestrictive.hashCode();
}
/**
diff --git a/core/java/android/app/admin/NoArgsPolicyKey.java b/core/java/android/app/admin/NoArgsPolicyKey.java
index 57b67d5..2c9d2be 100644
--- a/core/java/android/app/admin/NoArgsPolicyKey.java
+++ b/core/java/android/app/admin/NoArgsPolicyKey.java
@@ -16,8 +16,11 @@
package android.app.admin;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,6 +44,14 @@
this(source.readString());
}
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToBundle(Bundle bundle) {
+ bundle.putString(EXTRA_POLICY_KEY, getIdentifier());
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 4aa2e38..90ca052 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -19,6 +19,7 @@
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -119,7 +120,7 @@
*/
@Override
public void writeToBundle(Bundle bundle) {
- super.writeToBundle(bundle);
+ bundle.putString(EXTRA_POLICY_KEY, getIdentifier());
Bundle extraPolicyParams = new Bundle();
extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
extraPolicyParams.putString(EXTRA_PERMISSION_NAME, mPermissionName);
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 3469970..08b6110 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -18,6 +18,7 @@
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -101,7 +102,7 @@
*/
@Override
public void writeToBundle(Bundle bundle) {
- super.writeToBundle(bundle);
+ bundle.putString(EXTRA_POLICY_KEY, getIdentifier());
Bundle extraPolicyParams = new Bundle();
extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
diff --git a/core/java/android/app/admin/PolicyKey.java b/core/java/android/app/admin/PolicyKey.java
index a35f634..3544c19 100644
--- a/core/java/android/app/admin/PolicyKey.java
+++ b/core/java/android/app/admin/PolicyKey.java
@@ -16,8 +16,6 @@
package android.app.admin;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -39,8 +37,7 @@
*
* @hide
*/
-// This is ok as the constructor is hidden and all subclasses have implemented
-// Parcelable.
+// This is ok as the constructor is hidden and all subclasses have implemented Parcelable.
@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
@SystemApi
public abstract class PolicyKey implements Parcelable {
@@ -104,9 +101,7 @@
/**
* @hide
*/
- public void writeToBundle(Bundle bundle) {
- bundle.putString(EXTRA_POLICY_KEY, mIdentifier);
- }
+ public abstract void writeToBundle(Bundle bundle);
@Override
public boolean equals(@Nullable Object o) {
diff --git a/core/java/android/app/admin/PolicyState.java b/core/java/android/app/admin/PolicyState.java
index da71bb1..fa76bfa 100644
--- a/core/java/android/app/admin/PolicyState.java
+++ b/core/java/android/app/admin/PolicyState.java
@@ -66,7 +66,7 @@
PolicyValue<V> policyValue = source.readParcelable(PolicyValue.class.getClassLoader());
mPoliciesSetByAdmins.put(admin, policyValue);
}
- mCurrentResolvedPolicy = source.readParcelable((PolicyValue.class.getClassLoader()));
+ mCurrentResolvedPolicy = source.readParcelable(PolicyValue.class.getClassLoader());
mResolutionMechanism = source.readParcelable(ResolutionMechanism.class.getClassLoader());
}
@@ -87,7 +87,7 @@
*/
@Nullable
public V getCurrentResolvedPolicy() {
- return mCurrentResolvedPolicy.getValue();
+ return mCurrentResolvedPolicy == null ? null : mCurrentResolvedPolicy.getValue();
}
/**
diff --git a/core/java/android/app/admin/PolicyUpdateResult.java b/core/java/android/app/admin/PolicyUpdateResult.java
index a6d0ebf..79a76f2 100644
--- a/core/java/android/app/admin/PolicyUpdateResult.java
+++ b/core/java/android/app/admin/PolicyUpdateResult.java
@@ -44,6 +44,9 @@
/**
* Result code to indicate that the policy has not been enforced or has changed because another
* admin has set a conflicting policy on the device.
+ *
+ * <p>The system will automatically try to enforce the policy when it can without additional
+ * calls from the admin.
*/
public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1;
@@ -56,6 +59,22 @@
public static final int RESULT_POLICY_CLEARED = 2;
/**
+ * Result code to indicate that the policy set by the admin has not been enforced because the
+ * local storage has reached its max limit.
+ *
+ * <p>The system will NOT try to automatically store and enforce this policy again.
+ */
+ public static final int RESULT_FAILURE_STORAGE_LIMIT_REACHED = 3;
+
+ /**
+ * Result code to indicate that the policy set by the admin has not been enforced because of a
+ * permanent hardware limitation/issue.
+ *
+ * <p>The system will NOT try to automatically store and enforce this policy again.
+ */
+ public static final int RESULT_FAILURE_HARDWARE_LIMITATION = 4;
+
+ /**
* Reason codes for {@link #getResultCode()}.
*
* @hide
@@ -65,7 +84,9 @@
RESULT_FAILURE_UNKNOWN,
RESULT_SUCCESS,
RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
- RESULT_POLICY_CLEARED
+ RESULT_POLICY_CLEARED,
+ RESULT_FAILURE_STORAGE_LIMIT_REACHED,
+ RESULT_FAILURE_HARDWARE_LIMITATION
})
public @interface ResultCode {}
diff --git a/core/java/android/app/admin/PolicyUpdatesReceiver.java b/core/java/android/app/admin/PolicyUpdatesReceiver.java
index 67de04c..be32d83 100644
--- a/core/java/android/app/admin/PolicyUpdatesReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdatesReceiver.java
@@ -104,6 +104,14 @@
"android.app.admin.extra.INTENT_FILTER";
/**
+ * A string extra holding the account type this policy applies to, (see
+ * {@link PolicyUpdatesReceiver#onPolicyChanged} and
+ * {@link PolicyUpdatesReceiver#onPolicySetResult})
+ */
+ public static final String EXTRA_ACCOUNT_TYPE =
+ "android.app.admin.extra.ACCOUNT_TYPE";
+
+ /**
* @hide
*/
public static final String EXTRA_POLICY_CHANGED_KEY =
@@ -214,7 +222,7 @@
* send updates.
*
* @param context the running context as per {@link #onReceive}
- * @param policyKey Key to identify which policy this callback relates to.
+ * @param policyIdentifier Key to identify which policy this callback relates to.
* @param additionalPolicyParams Bundle containing additional params that may be required to
* identify some of the policy
* (e.g. {@link PolicyUpdatesReceiver#EXTRA_PACKAGE_NAME}
@@ -230,7 +238,7 @@
*/
public void onPolicySetResult(
@NonNull Context context,
- @NonNull String policyKey,
+ @NonNull String policyIdentifier,
@NonNull Bundle additionalPolicyParams,
@NonNull TargetUser targetUser,
@NonNull PolicyUpdateResult policyUpdateResult) {}
@@ -247,7 +255,7 @@
* send updates.
*
* @param context the running context as per {@link #onReceive}
- * @param policyKey Key to identify which policy this callback relates to.
+ * @param policyIdentifier Key to identify which policy this callback relates to.
* @param additionalPolicyParams Bundle containing additional params that may be required to
* identify some of the policy
* (e.g. {@link PolicyUpdatesReceiver#EXTRA_PACKAGE_NAME}
@@ -264,7 +272,7 @@
*/
public void onPolicyChanged(
@NonNull Context context,
- @NonNull String policyKey,
+ @NonNull String policyIdentifier,
@NonNull Bundle additionalPolicyParams,
@NonNull TargetUser targetUser,
@NonNull PolicyUpdateResult policyUpdateResult) {}
diff --git a/core/java/android/app/admin/RoleAuthority.java b/core/java/android/app/admin/RoleAuthority.java
index 7fdd118..ccb41c3 100644
--- a/core/java/android/app/admin/RoleAuthority.java
+++ b/core/java/android/app/admin/RoleAuthority.java
@@ -22,7 +22,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -38,15 +37,18 @@
private final Set<String> mRoles;
/**
- * @hide
+ * Constructor for a role authority that accepts the list of roles held by the admin.
*/
public RoleAuthority(@NonNull Set<String> roles) {
mRoles = new HashSet<>(Objects.requireNonNull(roles));
}
private RoleAuthority(Parcel source) {
- String[] roles = source.readStringArray();
- mRoles = roles == null ? new HashSet<>() : new HashSet<>(Arrays.stream(roles).toList());
+ mRoles = new HashSet<>();
+ int size = source.readInt();
+ for (int i = 0; i < size; i++) {
+ mRoles.add(source.readString());
+ }
}
/**
@@ -64,7 +66,10 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeArray(mRoles.toArray());
+ dest.writeInt(mRoles.size());
+ for (String role : mRoles) {
+ dest.writeString(role);
+ }
}
@Override
diff --git a/core/java/android/app/admin/StringSetUnion.java b/core/java/android/app/admin/StringSetUnion.java
index 730e6a2..a95b51e 100644
--- a/core/java/android/app/admin/StringSetUnion.java
+++ b/core/java/android/app/admin/StringSetUnion.java
@@ -17,6 +17,7 @@
package android.app.admin;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,6 +34,23 @@
@TestApi
public final class StringSetUnion extends ResolutionMechanism<Set<String>> {
+ /**
+ * Union resolution for policies represented {@code Set<String>} which resolves as the union of
+ * all sets.
+ */
+ @NonNull
+ public static final StringSetUnion STRING_SET_UNION = new StringSetUnion();
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ return o != null && getClass() == o.getClass();
+ }
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
@Override
public String toString() {
return "StringSetUnion {}";
diff --git a/core/java/android/app/admin/TopPriority.java b/core/java/android/app/admin/TopPriority.java
index e712274..edb93b2 100644
--- a/core/java/android/app/admin/TopPriority.java
+++ b/core/java/android/app/admin/TopPriority.java
@@ -17,11 +17,12 @@
package android.app.admin;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -36,26 +37,56 @@
@TestApi
public final class TopPriority<V> extends ResolutionMechanism<V> {
- private final List<String> mHighestToLowestPriorityAuthorities;
+ private final List<Authority> mHighestToLowestPriorityAuthorities;
/**
* @hide
*/
- public TopPriority(@NonNull List<String> highestToLowestPriorityAuthorities) {
+ public TopPriority(@NonNull List<Authority> highestToLowestPriorityAuthorities) {
mHighestToLowestPriorityAuthorities = Objects.requireNonNull(
highestToLowestPriorityAuthorities);
}
/**
+ * Returns an object with the specified order of highest to lowest authorities.
+ */
+ private TopPriority(@NonNull Parcel source) {
+ mHighestToLowestPriorityAuthorities = new ArrayList<>();
+ int size = source.readInt();
+ for (int i = 0; i < size; i++) {
+ mHighestToLowestPriorityAuthorities.add(
+ source.readParcelable(Authority.class.getClassLoader()));
+ }
+ }
+
+ /**
* Returns an ordered list of authorities from highest priority to lowest priority for a
* certain policy.
*/
@NonNull
- List<String> getHighestToLowestPriorityAuthorities() {
+ public List<Authority> getHighestToLowestPriorityAuthorities() {
return mHighestToLowestPriorityAuthorities;
}
@Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ try {
+ TopPriority<V> other = (TopPriority<V>) o;
+ return Objects.equals(
+ mHighestToLowestPriorityAuthorities, other.mHighestToLowestPriorityAuthorities);
+ } catch (ClassCastException exception) {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mHighestToLowestPriorityAuthorities.hashCode();
+ }
+
+ @Override
public String toString() {
return "TopPriority { mHighestToLowestPriorityAuthorities= "
+ mHighestToLowestPriorityAuthorities + " }";
@@ -67,7 +98,10 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStringArray(mHighestToLowestPriorityAuthorities.toArray(new String[0]));
+ dest.writeInt(mHighestToLowestPriorityAuthorities.size());
+ for (Authority authority : mHighestToLowestPriorityAuthorities) {
+ dest.writeParcelable(authority, flags);
+ }
}
@NonNull
@@ -75,9 +109,7 @@
new Parcelable.Creator<TopPriority<?>>() {
@Override
public TopPriority<?> createFromParcel(Parcel source) {
- String[] highestToLowestPriorityAuthorities = source.readStringArray();
- return new TopPriority<>(
- Arrays.stream(highestToLowestPriorityAuthorities).toList());
+ return new TopPriority<>(source);
}
@Override
diff --git a/core/java/android/app/admin/UnknownAuthority.java b/core/java/android/app/admin/UnknownAuthority.java
index 4492b96..fdad898 100644
--- a/core/java/android/app/admin/UnknownAuthority.java
+++ b/core/java/android/app/admin/UnknownAuthority.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
/**
@@ -32,11 +33,19 @@
public final class UnknownAuthority extends Authority {
/**
+ * Object representing an unknown authority.
+ *
* @hide
*/
+ @TestApi
+ @NonNull
public static final UnknownAuthority UNKNOWN_AUTHORITY = new UnknownAuthority();
- private UnknownAuthority() {}
+ /**
+ * Creates an authority that represents an admin that can set a policy but
+ * doesn't have a known authority (e.g. a system components).
+ */
+ public UnknownAuthority() {}
@Override
public String toString() {
@@ -45,12 +54,13 @@
@Override
public boolean equals(@Nullable Object o) {
- return super.equals(o);
+ if (this == o) return true;
+ return o != null && getClass() == o.getClass();
}
@Override
public int hashCode() {
- return super.hashCode();
+ return 0;
}
@Override
@@ -66,7 +76,7 @@
new Creator<UnknownAuthority>() {
@Override
public UnknownAuthority createFromParcel(Parcel source) {
- return new UnknownAuthority();
+ return UNKNOWN_AUTHORITY;
}
@Override
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
new file mode 100644
index 0000000..92014763
--- /dev/null
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * Class used to identify a policy that relates to a certain user restriction
+ * (See {@link DevicePolicyManager#addUserRestriction} and
+ * {@link DevicePolicyManager#addUserRestrictionGlobally}).
+ *
+ * @hide
+ */
+@SystemApi
+public final class UserRestrictionPolicyKey extends PolicyKey {
+
+ private final String mRestriction;
+
+ /**
+ * @hide
+ */
+ public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
+ super(identifier);
+ mRestriction = Objects.requireNonNull(restriction);
+ }
+
+ private UserRestrictionPolicyKey(Parcel source) {
+ this(source.readString(), source.readString());
+ }
+
+ /**
+ * Returns the user restriction associated with this policy.
+ */
+ @NonNull
+ public String getRestriction() {
+ return mRestriction;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToBundle(Bundle bundle) {
+ bundle.putString(EXTRA_POLICY_KEY, getIdentifier());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getIdentifier());
+ dest.writeString(mRestriction);
+ }
+
+ @NonNull
+ public static final Creator<UserRestrictionPolicyKey> CREATOR =
+ new Creator<UserRestrictionPolicyKey>() {
+ @Override
+ public UserRestrictionPolicyKey createFromParcel(Parcel source) {
+ return new UserRestrictionPolicyKey(source);
+ }
+
+ @Override
+ public UserRestrictionPolicyKey[] newArray(int size) {
+ return new UserRestrictionPolicyKey[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "UserRestrictionPolicyKey " + getIdentifier();
+ }
+}
diff --git a/core/java/android/app/search/ISearchUiManager.aidl b/core/java/android/app/search/ISearchUiManager.aidl
index fefbd5a..4cf0b81 100644
--- a/core/java/android/app/search/ISearchUiManager.aidl
+++ b/core/java/android/app/search/ISearchUiManager.aidl
@@ -38,8 +38,6 @@
void registerEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
- void requestEmptyQueryResultUpdate(in SearchSessionId sessionId);
-
void unregisterEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
void destroySearchSession(in SearchSessionId sessionId);
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 9e0a1d0..0dbd81e 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -225,32 +225,6 @@
}
/**
- * Requests the search ui service to dispatch a new set of search targets to the pre-registered
- * callback for zero state. Zero state means when user entered search ui but not issued any
- * query yet. This method can be used for client to sync up with server data if they think data
- * might be out of sync, for example, after restart.
- * Pre-register a callback with
- * {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
- * is required before calling this method. Otherwise this is no-op.
- *
- * @see {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
- * @see {@link SearchSession.Callback#onTargetsAvailable(List)}.
- */
- public void requestEmptyQueryResultUpdate() {
- if (mIsClosed.get()) {
- throw new IllegalStateException("This client has already been destroyed.");
- }
-
- try {
- mInterface.requestEmptyQueryResultUpdate(mSessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to request empty query result update", e);
- e.rethrowAsRuntimeException();
- }
- }
-
-
- /**
* Destroys the client and unregisters the callback. Any method on this class after this call
* will throw {@link IllegalStateException}.
*
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 8842955..5df2d5e 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -491,7 +491,7 @@
* @param associationId id of the device association.
* @param flags system data types to be enabled.
*/
- public void enableSystemDataSync(int associationId, @DataSyncTypes int flags) {
+ public void enableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) {
if (!checkFeaturePresent()) {
return;
}
@@ -513,7 +513,7 @@
* @param associationId id of the device association.
* @param flags system data types to be disabled.
*/
- public void disableSystemDataSync(int associationId, @DataSyncTypes int flags) {
+ public void disableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) {
if (!checkFeaturePresent()) {
return;
}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 9ab7cf9..12882a2 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -20,7 +20,7 @@
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
-import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.IntentFilter;
@@ -112,16 +112,10 @@
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
- * Creates a virtual sensor, capable of injecting sensor events into the system.
+ * Returns all virtual sensors for this device.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
-
- /**
- * Removes the sensor corresponding to the given token from the system.
- */
- @EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void unregisterSensor(IBinder token);
+ List<VirtualSensor> getVirtualSensorList();
/**
* Sends an event to the virtual sensor corresponding to the given token.
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 29e2231..ae43c6e 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -35,7 +35,6 @@
import android.companion.virtual.camera.VirtualCameraDevice;
import android.companion.virtual.camera.VirtualCameraInput;
import android.companion.virtual.sensor.VirtualSensor;
-import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -428,8 +427,6 @@
};
@Nullable
private VirtualCameraDevice mVirtualCameraDevice;
- @NonNull
- private final List<VirtualSensor> mVirtualSensors = new ArrayList<>();
@Nullable
private VirtualAudioDevice mVirtualAudioDevice;
@@ -448,10 +445,6 @@
params,
mActivityListenerBinder,
mSoundEffectListener);
- final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
- for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
- mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
- }
}
/**
@@ -478,20 +471,19 @@
}
/**
- * Returns this device's sensor with the given type and name, if any.
+ * Returns this device's sensors.
*
* @see VirtualDeviceParams.Builder#addVirtualSensorConfig
*
- * @param type The type of the sensor.
- * @param name The name of the sensor.
- * @return The matching sensor if found, {@code null} otherwise.
+ * @return A list of all sensors for this device, or an empty list if no sensors exist.
*/
- @Nullable
- public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
- return mVirtualSensors.stream()
- .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
- .findAny()
- .orElse(null);
+ @NonNull
+ public List<VirtualSensor> getVirtualSensorList() {
+ try {
+ return mVirtualDevice.getVirtualSensorList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -938,28 +930,6 @@
}
/**
- * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
- * internal use, since device sensors must remain valid for the entire lifetime of the
- * device.
- *
- * @param config The configuration of the sensor.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- @NonNull
- public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
- Objects.requireNonNull(config);
- try {
- final IBinder token = new Binder(
- "android.hardware.sensor.VirtualSensor:" + config.getName());
- mVirtualDevice.createVirtualSensor(token, config);
- return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Adds an activity listener to listen for events such as top activity change or virtual
* display task stack became empty.
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d4a0a08..d8076b5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -19,11 +19,18 @@
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.companion.virtual.sensor.IVirtualSensorCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.os.Parcel;
@@ -37,11 +44,13 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* Params that can be configured when creating virtual devices.
@@ -190,6 +199,7 @@
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
+ @Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
@RecentsPolicy
private final int mDefaultRecentsPolicy;
private final int mAudioPlaybackSessionId;
@@ -207,6 +217,7 @@
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
+ @Nullable IVirtualSensorCallback virtualSensorCallback,
@RecentsPolicy int defaultRecentsPolicy,
int audioPlaybackSessionId,
int audioRecordingSessionId) {
@@ -224,6 +235,7 @@
mName = name;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
+ mVirtualSensorCallback = virtualSensorCallback;
mDefaultRecentsPolicy = defaultRecentsPolicy;
mAudioPlaybackSessionId = audioPlaybackSessionId;
mAudioRecordingSessionId = audioRecordingSessionId;
@@ -244,6 +256,8 @@
mDevicePolicies = parcel.readSparseIntArray();
mVirtualSensorConfigs = new ArrayList<>();
parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
+ mVirtualSensorCallback =
+ IVirtualSensorCallback.Stub.asInterface(parcel.readStrongBinder());
mDefaultRecentsPolicy = parcel.readInt();
mAudioPlaybackSessionId = parcel.readInt();
mAudioRecordingSessionId = parcel.readInt();
@@ -372,6 +386,15 @@
}
/**
+ * Returns the callback to get notified about changes in the sensor listeners.
+ * @hide
+ */
+ @Nullable
+ public IVirtualSensorCallback getVirtualSensorCallback() {
+ return mVirtualSensorCallback;
+ }
+
+ /**
* Returns the policy of how to handle activities in recents.
*
* @see RecentsPolicy
@@ -417,6 +440,8 @@
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
+ dest.writeStrongBinder(
+ mVirtualSensorCallback != null ? mVirtualSensorCallback.asBinder() : null);
dest.writeInt(mDefaultRecentsPolicy);
dest.writeInt(mAudioPlaybackSessionId);
dest.writeInt(mAudioRecordingSessionId);
@@ -522,11 +547,38 @@
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
- @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
private int mDefaultRecentsPolicy;
private int mAudioPlaybackSessionId = AUDIO_SESSION_ID_GENERATE;
private int mAudioRecordingSessionId = AUDIO_SESSION_ID_GENERATE;
+ @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+ @Nullable
+ private IVirtualSensorCallback mVirtualSensorCallback;
+
+ private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final VirtualSensorCallback mCallback;
+
+ VirtualSensorCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
+ @NonNull VirtualSensorCallback callback) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
+ int samplingPeriodMicros, int batchReportLatencyMicros) {
+ final Duration samplingPeriod =
+ Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
+ final Duration batchReportingLatency =
+ Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
+ mExecutor.execute(() -> mCallback.onConfigurationChanged(
+ sensor, enabled, samplingPeriod, batchReportingLatency));
+ }
+ }
+
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
* is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
@@ -731,6 +783,24 @@
}
/**
+ * Sets the callback to get notified about changes in the sensor listeners.
+ *
+ * @param executor The executor where the callback is executed on.
+ * @param callback The callback to get notified when the state of the sensor
+ * listeners has changed, see {@link VirtualSensorCallback}
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setVirtualSensorCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull VirtualSensorCallback callback) {
+ mVirtualSensorCallback = new VirtualSensorCallbackDelegate(
+ Objects.requireNonNull(executor),
+ Objects.requireNonNull(callback));
+ return this;
+ }
+
+ /**
* Sets the policy to indicate how activities are handled in recents.
*
* @param defaultRecentsPolicy A policy specifying how to handle activities in recents.
@@ -798,12 +868,17 @@
*/
@NonNull
public VirtualDeviceParams build() {
- if (!mVirtualSensorConfigs.isEmpty()
- && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
- != DEVICE_POLICY_CUSTOM)) {
- throw new IllegalArgumentException(
- "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
- + "virtual sensors.");
+ if (!mVirtualSensorConfigs.isEmpty()) {
+ if (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
+ != DEVICE_POLICY_CUSTOM) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+ + "virtual sensors.");
+ }
+ if (mVirtualSensorCallback == null) {
+ throw new IllegalArgumentException(
+ "VirtualSensorCallback is required for creating virtual sensors.");
+ }
}
if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
@@ -837,6 +912,7 @@
mName,
mDevicePolicies,
mVirtualSensorConfigs,
+ mVirtualSensorCallback,
mDefaultRecentsPolicy,
mAudioPlaybackSessionId,
mAudioRecordingSessionId);
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
similarity index 66%
rename from core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
rename to core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
index b99cc7e..7da9c32 100644
--- a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
@@ -16,20 +16,24 @@
package android.companion.virtual.sensor;
+import android.companion.virtual.sensor.VirtualSensor;
+
/**
- * Interface for notification of listener registration changes for a virtual sensor.
+ * Interface for notifying the sensor owner about whether and how sensor events should be injected.
*
* @hide
*/
-oneway interface IVirtualSensorStateChangeCallback {
+oneway interface IVirtualSensorCallback {
/**
- * Called when the registered listeners to a virtual sensor have changed.
+ * Called when the requested sensor event injection parameters have changed.
*
+ * @param sensor The sensor whose requested injection parameters have changed.
* @param enabled Whether the sensor is enabled.
* @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
* @param batchReportingLatencyMicros The requested maximum time interval in microseconds
* between the delivery of two batches of sensor events.
*/
- void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
+ void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros);
}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.aidl b/core/java/android/companion/virtual/sensor/VirtualSensor.aidl
new file mode 100644
index 0000000..ccb597a
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensor;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index 58a5387..bda44d4 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -22,10 +22,10 @@
import android.companion.virtual.IVirtualDevice;
import android.hardware.Sensor;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
-import java.time.Duration;
-
/**
* Representation of a sensor on a remote device, capable of sending events, such as an
* accelerometer or a gyroscope.
@@ -35,24 +35,8 @@
* @hide
*/
@SystemApi
-public class VirtualSensor {
-
- /**
- * Interface for notification of listener registration changes for a virtual sensor.
- */
- public interface SensorStateChangeCallback {
- /**
- * Called when the registered listeners to a virtual sensor have changed.
- *
- * @param enabled Whether the sensor is enabled.
- * @param samplingPeriod The requested sampling period of the sensor.
- * @param batchReportLatency The requested maximum time interval between the delivery of two
- * batches of sensor events.
- */
- void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
- @NonNull Duration batchReportLatency);
- }
-
+public final class VirtualSensor implements Parcelable {
+ private final int mHandle;
private final int mType;
private final String mName;
private final IVirtualDevice mVirtualDevice;
@@ -61,13 +45,32 @@
/**
* @hide
*/
- public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
+ public VirtualSensor(int handle, int type, String name, IVirtualDevice virtualDevice,
+ IBinder token) {
+ mHandle = handle;
mType = type;
mName = name;
mVirtualDevice = virtualDevice;
mToken = token;
}
+ private VirtualSensor(Parcel parcel) {
+ mHandle = parcel.readInt();
+ mType = parcel.readInt();
+ mName = parcel.readString8();
+ mVirtualDevice = IVirtualDevice.Stub.asInterface(parcel.readStrongBinder());
+ mToken = parcel.readStrongBinder();
+ }
+
+ /**
+ * Returns the unique handle of the sensor.
+ *
+ * @hide
+ */
+ public int getHandle() {
+ return mHandle;
+ }
+
/**
* Returns the type of the sensor.
*
@@ -87,6 +90,32 @@
}
/**
+ * Returns the identifier of the
+ * {@link android.companion.virtual.VirtualDeviceManager.VirtualDevice} this sensor belongs to.
+ */
+ public int getDeviceId() {
+ try {
+ return mVirtualDevice.getDeviceId();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mHandle);
+ parcel.writeInt(mType);
+ parcel.writeString8(mName);
+ parcel.writeStrongBinder(mVirtualDevice.asBinder());
+ parcel.writeStrongBinder(mToken);
+ }
+
+ /**
* Send a sensor event to the system.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -97,4 +126,16 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @NonNull
+ public static final Parcelable.Creator<VirtualSensor> CREATOR =
+ new Parcelable.Creator<VirtualSensor>() {
+ public VirtualSensor createFromParcel(Parcel in) {
+ return new VirtualSensor(in);
+ }
+
+ public VirtualSensor[] newArray(int size) {
+ return new VirtualSensor[size];
+ }
+ };
}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
new file mode 100644
index 0000000..e097189
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.time.Duration;
+
+/**
+ * Interface for notifying the sensor owner about whether and how sensor events should be injected.
+ *
+ * <p>This callback can be used for controlling the sensor event injection - e.g. if the sensor is
+ * not enabled, then no events should be injected. Similarly, the rate and delay of the injected
+ * events that the registered listeners expect are specified here.
+ *
+ * <p>The callback is tied to the VirtualDevice's lifetime as the virtual sensors are created when
+ * the device is created and destroyed when the device is destroyed.
+ *
+ * @hide
+ */
+@SystemApi
+public interface VirtualSensorCallback {
+ /**
+ * Called when the requested sensor event injection parameters have changed.
+ *
+ * <p>This is effectively called when the registered listeners to a virtual sensor have changed.
+ *
+ * @param sensor The sensor whose requested injection parameters have changed.
+ * @param enabled Whether the sensor is enabled. True if any listeners are currently registered,
+ * and false otherwise.
+ * @param samplingPeriod The requested sampling period of the sensor.
+ * @param batchReportLatency The requested maximum time interval between the delivery of two
+ * batches of sensor events.
+ */
+ void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
+ @NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency);
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index eb2f9dd..6d45365 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -16,20 +16,15 @@
package android.companion.virtual.sensor;
-import static java.util.concurrent.TimeUnit.MICROSECONDS;
-import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.hardware.Sensor;
import android.os.Parcel;
import android.os.Parcelable;
-import java.time.Duration;
import java.util.Objects;
-import java.util.concurrent.Executor;
/**
* Configuration for creation of a virtual sensor.
@@ -44,23 +39,17 @@
private final String mName;
@Nullable
private final String mVendor;
- @Nullable
- private final IVirtualSensorStateChangeCallback mStateChangeCallback;
- private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
- @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
+ private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor) {
mType = type;
mName = name;
mVendor = vendor;
- mStateChangeCallback = stateChangeCallback;
}
private VirtualSensorConfig(@NonNull Parcel parcel) {
mType = parcel.readInt();
mName = parcel.readString8();
mVendor = parcel.readString8();
- mStateChangeCallback =
- IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
}
@Override
@@ -73,8 +62,6 @@
parcel.writeInt(mType);
parcel.writeString8(mName);
parcel.writeString8(mVendor);
- parcel.writeStrongBinder(
- mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
}
/**
@@ -105,15 +92,6 @@
}
/**
- * Returns the callback to get notified about changes in the sensor listeners.
- * @hide
- */
- @Nullable
- public IVirtualSensorStateChangeCallback getStateChangeCallback() {
- return mStateChangeCallback;
- }
-
- /**
* Builder for {@link VirtualSensorConfig}.
*/
public static final class Builder {
@@ -123,32 +101,6 @@
private final String mName;
@Nullable
private String mVendor;
- @Nullable
- private IVirtualSensorStateChangeCallback mStateChangeCallback;
-
- private static class SensorStateChangeCallbackDelegate
- extends IVirtualSensorStateChangeCallback.Stub {
- @NonNull
- private final Executor mExecutor;
- @NonNull
- private final VirtualSensor.SensorStateChangeCallback mCallback;
-
- SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
- @NonNull VirtualSensor.SensorStateChangeCallback callback) {
- mCallback = callback;
- mExecutor = executor;
- }
- @Override
- public void onStateChanged(boolean enabled, int samplingPeriodMicros,
- int batchReportLatencyMicros) {
- final Duration samplingPeriod =
- Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
- final Duration batchReportingLatency =
- Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
- mExecutor.execute(() -> mCallback.onStateChanged(
- enabled, samplingPeriod, batchReportingLatency));
- }
- }
/**
* Creates a new builder.
@@ -167,7 +119,7 @@
*/
@NonNull
public VirtualSensorConfig build() {
- return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
+ return new VirtualSensorConfig(mType, mName, mVendor);
}
/**
@@ -178,24 +130,6 @@
mVendor = vendor;
return this;
}
-
- /**
- * Sets the callback to get notified about changes in the sensor listeners.
- *
- * @param executor The executor where the callback is executed on.
- * @param callback The callback to get notified when the state of the sensor
- * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
- */
- @SuppressLint("MissingGetterMatchingBuilder")
- @NonNull
- public VirtualSensorConfig.Builder setStateChangeCallback(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull VirtualSensor.SensorStateChangeCallback callback) {
- mStateChangeCallback = new SensorStateChangeCallbackDelegate(
- Objects.requireNonNull(executor),
- Objects.requireNonNull(callback));
- return this;
- }
}
@NonNull
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ffe73d6..7be00a0 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -679,8 +679,9 @@
public static final int PRIVATE_FLAG_PROFILEABLE_BY_SHELL = 1 << 23;
/**
- * Indicates whether this package requires access to non-SDK APIs.
- * Only system apps and tests are allowed to use this property.
+ * Indicates whether this application has declared its user data as fragile,
+ * causing the system to prompt the user on whether to keep the user data
+ * on uninstall.
* @hide
*/
public static final int PRIVATE_FLAG_HAS_FRAGILE_USER_DATA = 1 << 24;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index dfaa065..baf10ed 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -575,8 +575,6 @@
boolean performDexOptSecondary(String packageName,
String targetCompilerFilter, boolean force);
- void forceDexOpt(String packageName);
-
int getMoveStatus(int moveId);
void registerMoveCallback(in IPackageMoveObserver callback);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8f864f4..cfd291f 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,8 +30,6 @@
import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
-import static com.android.internal.util.XmlUtils.writeStringAttribute;
-
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
@@ -84,7 +82,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ExceptionUtils;
-import android.util.Log;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.ArrayUtils;
@@ -92,7 +89,6 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlSerializer;
import java.io.Closeable;
import java.io.File;
@@ -2313,11 +2309,6 @@
private final ArrayMap<String, Integer> mPermissionStates;
/**
- * @see #getFinalPermissionStates()
- */
- private ArrayMap<String, Integer> mFinalPermissionStates;
-
- /**
* Construct parameters for a new package install session.
*
* @param mode one of {@link #MODE_FULL_INSTALL} or
@@ -2563,11 +2554,6 @@
+ (permissionName == null ? "null" : "empty"));
}
- if (mFinalPermissionStates != null) {
- Log.wtf(TAG, "Requested permission " + permissionName + " but final permissions"
- + " were already decided for this session: " + mFinalPermissionStates);
- }
-
switch (state) {
case PERMISSION_STATE_DEFAULT:
mPermissionStates.remove(permissionName);
@@ -3008,48 +2994,10 @@
}
}
- /**
- * This is only for use by system server. If you need the actual grant state, use
- * {@link #getFinalPermissionStates()}.
- * <p/>
- * This is implemented here to avoid exposing the raw permission sets to external callers,
- * so that enforcement done in the either of the final methods is the single source of truth
- * for default grant/deny policy.
- *
- * @hide
- */
- public void writePermissionStateXml(@NonNull TypedXmlSerializer out,
- @NonNull String grantTag, @NonNull String denyTag, @NonNull String attrName)
- throws IOException {
- for (int index = 0; index < mPermissionStates.size(); index++) {
- var permissionName = mPermissionStates.keyAt(index);
- var state = mPermissionStates.valueAt(index);
- String tag = state == PERMISSION_STATE_GRANTED ? grantTag : denyTag;
- out.startTag(null, tag);
- writeStringAttribute(out, attrName, permissionName);
- out.endTag(null, tag);
- }
- }
-
- /**
- * Snapshot of final permission states taken when this method is first called, to separate
- * what the caller wanted and the effective state that should be applied to the session.
- *
- * This prevents someone from adding more permissions after the fact.
- *
- * @hide
- */
+ /** @hide */
@NonNull
- public ArrayMap<String, Integer> getFinalPermissionStates() {
- if (mFinalPermissionStates == null) {
- mFinalPermissionStates = new ArrayMap<>(mPermissionStates);
- if (!mFinalPermissionStates.containsKey(
- Manifest.permission.USE_FULL_SCREEN_INTENT)) {
- mFinalPermissionStates.put(Manifest.permission.USE_FULL_SCREEN_INTENT,
- PERMISSION_STATE_GRANTED);
- }
- }
- return mFinalPermissionStates;
+ public ArrayMap<String, Integer> getPermissionStates() {
+ return mPermissionStates;
}
/** @hide */
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 49d21da..90ed4ce 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -246,8 +246,17 @@
/**
* Constant corresponding to {@code mediaProjection} in
- * the {@link android.R.attr#foregroundServiceType} attribute.
- * Managing a media projection session, e.g for screen recording or taking screenshots.
+ * the {@link android.R.attr#foregroundServiceType foregroundServiceType} attribute.
+ *
+ * <p>
+ * To capture through {@link android.media.projection.MediaProjection}, an app must start a
+ * foreground service with the type corresponding to this constant. This type should only be
+ * used for {@link android.media.projection.MediaProjection}. Capturing screen contents via
+ * {@link android.media.projection.MediaProjection#createVirtualDisplay(String, int, int, int,
+ * int, android.view.Surface, android.hardware.display.VirtualDisplay.Callback,
+ * android.os.Handler) createVirtualDisplay} conveniently allows recording, presenting screen
+ * contents into a meeting, taking screenshots, or several other scenarios.
+ * </p>
*
* <p>Starting foreground service with this type from apps targeting API level
* {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 709fa60..41c406d 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -614,8 +614,15 @@
boolean status = true;
synchronized (mInterfaceLock) {
try {
- mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
- mInitialized = true;
+ if (mSessionProcessor != null) {
+ mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
+ mInitialized = true;
+ } else {
+ Log.v(TAG, "Failed to start capture session, session released before " +
+ "extension start!");
+ status = false;
+ mCaptureSession.close();
+ }
} catch (RemoteException e) {
Log.e(TAG, "Failed to start capture session,"
+ " extension service does not respond!");
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 7164dc3..067d0c5 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -541,7 +541,8 @@
EVENT_FLAG_DISPLAY_ADDED,
EVENT_FLAG_DISPLAY_CHANGED,
EVENT_FLAG_DISPLAY_REMOVED,
- EVENT_FLAG_DISPLAY_BRIGHTNESS
+ EVENT_FLAG_DISPLAY_BRIGHTNESS,
+ EVENT_FLAG_HDR_SDR_RATIO_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventsMask {}
@@ -584,6 +585,19 @@
*/
public static final long EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 3;
+ /**
+ * Event flag to register for a display's hdr/sdr ratio changes. This notification is sent
+ * through the {@link DisplayListener#onDisplayChanged} callback method. New hdr/sdr
+ * values can be retrieved via {@link Display#getHdrSdrRatio()}.
+ *
+ * Requires that {@link Display#isHdrSdrRatioAvailable()} is true.
+ *
+ * @see #registerDisplayListener(DisplayListener, Handler, long)
+ *
+ * @hide
+ */
+ public static final long EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 4;
+
/** @hide */
public DisplayManager(Context context) {
mContext = context;
@@ -1429,6 +1443,7 @@
* hdrConversionMode.conversionMode is not {@link HdrConversionMode#HDR_CONVERSION_FORCE}.
*
* @see #getHdrConversionMode
+ * @see #getHdrConversionModeSetting
* @see #getSupportedHdrOutputTypes
* @hide
*/
@@ -1440,9 +1455,14 @@
/**
* Returns the {@link HdrConversionMode} of the device, which is set by the user.
+
+ * The HDR conversion mode chosen by user which considers the app override is returned. Apps can
+ * override HDR conversion using
+ * {@link android.view.WindowManager.LayoutParams#disableHdrConversion}.
*
* @see #setHdrConversionMode
* @see #getSupportedHdrOutputTypes
+ * @see #getHdrConversionModeSetting()
* @hide
*/
@TestApi
@@ -1452,6 +1472,23 @@
}
/**
+ * Returns the {@link HdrConversionMode} of the device, which is set by the user.
+
+ * The HDR conversion mode chosen by user is returned irrespective of whether HDR conversion
+ * is disabled by an app.
+ *
+ * @see #setHdrConversionMode
+ * @see #getSupportedHdrOutputTypes
+ * @see #getHdrConversionMode()
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public HdrConversionMode getHdrConversionModeSetting() {
+ return mGlobal.getHdrConversionModeSetting();
+ }
+
+ /**
* Returns the HDR output types supported by the device.
*
* @see #getHdrConversionMode
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index d9db177..f419ae4 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -38,6 +38,7 @@
import android.media.projection.IMediaProjection;
import android.media.projection.MediaProjection;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -56,11 +57,12 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Manager communication with the display manager service on behalf of
@@ -82,11 +84,12 @@
// orientation change before the display info cache has actually been invalidated.
private static final boolean USE_CACHE = false;
- @IntDef(prefix = {"SWITCHING_TYPE_"}, value = {
+ @IntDef(prefix = {"EVENT_DISPLAY_"}, flag = true, value = {
EVENT_DISPLAY_ADDED,
EVENT_DISPLAY_CHANGED,
EVENT_DISPLAY_REMOVED,
- EVENT_DISPLAY_BRIGHTNESS_CHANGED
+ EVENT_DISPLAY_BRIGHTNESS_CHANGED,
+ EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayEvent {}
@@ -95,6 +98,7 @@
public static final int EVENT_DISPLAY_CHANGED = 2;
public static final int EVENT_DISPLAY_REMOVED = 3;
public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4;
+ public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5;
@UnsupportedAppUsage
private static DisplayManagerGlobal sInstance;
@@ -109,7 +113,8 @@
private DisplayManagerCallback mCallback;
private @EventsMask long mRegisteredEventsMask = 0;
- private final ArrayList<DisplayListenerDelegate> mDisplayListeners = new ArrayList<>();
+ private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners =
+ new CopyOnWriteArrayList<>();
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
private final ColorSpace mWideColorSpace;
@@ -315,6 +320,19 @@
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventsMask long eventsMask) {
+ Looper looper = getLooperForHandler(handler);
+ Handler springBoard = new Handler(looper);
+ registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask);
+ }
+
+ /**
+ * Register a listener for display-related changes.
+ *
+ * @param listener The listener that will be called when display changes occur.
+ * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
+ */
+ public void registerDisplayListener(@NonNull DisplayListener listener,
+ @NonNull Executor executor, @EventsMask long eventsMask) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -326,8 +344,7 @@
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index < 0) {
- Looper looper = getLooperForHandler(handler);
- mDisplayListeners.add(new DisplayListenerDelegate(listener, looper, eventsMask));
+ mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask));
registerCallbackIfNeededLocked();
} else {
mDisplayListeners.get(index).setEventsMask(eventsMask);
@@ -408,6 +425,7 @@
}
private void handleDisplayEvent(int displayId, @DisplayEvent int event) {
+ final DisplayInfo info;
synchronized (mLock) {
if (USE_CACHE) {
mDisplayInfoCache.remove(displayId);
@@ -417,11 +435,7 @@
}
}
- final int numListeners = mDisplayListeners.size();
- DisplayInfo info = getDisplayInfo(displayId);
- for (int i = 0; i < numListeners; i++) {
- mDisplayListeners.get(i).sendDisplayEvent(displayId, event, info);
- }
+ info = getDisplayInfoLocked(displayId);
if (event == EVENT_DISPLAY_CHANGED && mDispatchNativeCallbacks) {
// Choreographer only supports a single display, so only dispatch refresh rate
// changes for the default display.
@@ -438,6 +452,11 @@
}
}
}
+ // Accepting an Executor means the listener may be synchronously invoked, so we must
+ // not be holding mLock when we do so
+ for (DisplayListenerDelegate listener : mDisplayListeners) {
+ listener.sendDisplayEvent(displayId, event, info);
+ }
}
public void startWifiDisplayScan() {
@@ -990,6 +1009,19 @@
}
/**
+ * Returns the {@link HdrConversionMode} of the device, which is set by the user.
+ * The HDR conversion mode chosen by user is returned irrespective of whether HDR conversion
+ * is disabled by an app.
+ */
+ public HdrConversionMode getHdrConversionModeSetting() {
+ try {
+ return mDm.getHdrConversionModeSetting();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the {@link HdrConversionMode} of the device.
*/
public HdrConversionMode getHdrConversionMode() {
@@ -1075,34 +1107,42 @@
}
}
- private static final class DisplayListenerDelegate extends Handler {
+ private static final class DisplayListenerDelegate {
public final DisplayListener mListener;
public volatile long mEventsMask;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
+ private final Executor mExecutor;
+ private AtomicLong mGenerationId = new AtomicLong(1);
- DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper,
+ DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
@EventsMask long eventsMask) {
- super(looper, null, true /*async*/);
+ mExecutor = executor;
mListener = listener;
mEventsMask = eventsMask;
}
public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) {
- Message msg = obtainMessage(event, displayId, 0, info);
- sendMessage(msg);
+ long generationId = mGenerationId.get();
+ Message msg = Message.obtain(null, event, displayId, 0, info);
+ mExecutor.execute(() -> {
+ // If the generation id's don't match we were canceled but still need to recycle()
+ if (generationId == mGenerationId.get()) {
+ handleMessage(msg);
+ }
+ msg.recycle();
+ });
}
public void clearEvents() {
- removeCallbacksAndMessages(null);
+ mGenerationId.incrementAndGet();
}
public void setEventsMask(@EventsMask long newEventsMask) {
mEventsMask = newEventsMask;
}
- @Override
- public void handleMessage(Message msg) {
+ private void handleMessage(Message msg) {
if (DEBUG) {
Trace.beginSection(
"DisplayListenerDelegate(" + eventToString(msg.what)
@@ -1134,6 +1174,11 @@
mListener.onDisplayRemoved(msg.arg1);
}
break;
+ case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
+ if ((mEventsMask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) {
+ mListener.onDisplayChanged(msg.arg1);
+ }
+ break;
}
if (DEBUG) {
Trace.endSection();
@@ -1255,6 +1300,8 @@
return "REMOVED";
case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
return "BRIGHTNESS_CHANGED";
+ case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
+ return "HDR_SDR_RATIO_CHANGED";
}
return "UNKNOWN";
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 2bd0606..d6df033 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -234,13 +234,14 @@
* @param requestedMinimalPostProcessing The preferred minimal post processing setting for the
* display. This is true when there is at least one visible window that wants minimal post
* processng on.
+ * @param disableHdrConversion The preferred HDR conversion setting for the window.
* @param inTraversal True if called from WindowManagerService during a window traversal
* prior to call to performTraversalInTransactionFromWindowManager.
*/
public abstract void setDisplayProperties(int displayId, boolean hasContent,
float requestedRefreshRate, int requestedModeId, float requestedMinRefreshRate,
float requestedMaxRefreshRate, boolean requestedMinimalPostProcessing,
- boolean inTraversal);
+ boolean disableHdrConversion, boolean inTraversal);
/**
* Applies an offset to the contents of a display, for example to avoid burn-in.
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 0a44f85..a3b7b51 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -180,6 +180,7 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MODIFY_HDR_CONVERSION_MODE)")
void setHdrConversionMode(in HdrConversionMode hdrConversionMode);
+ HdrConversionMode getHdrConversionModeSetting();
HdrConversionMode getHdrConversionMode();
int[] getSupportedHdrOutputTypes();
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 657541c..2ea9ea0 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -51,11 +51,9 @@
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
-import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
@@ -215,30 +213,6 @@
"android.hardware.input.metadata.KEYBOARD_LAYOUTS";
/**
- * Pointer Speed: The minimum (slowest) pointer speed (-7).
- * @hide
- */
- public static final int MIN_POINTER_SPEED = -7;
-
- /**
- * Pointer Speed: The maximum (fastest) pointer speed (7).
- * @hide
- */
- public static final int MAX_POINTER_SPEED = 7;
-
- /**
- * Pointer Speed: The default pointer speed (0).
- * @hide
- */
- public static final int DEFAULT_POINTER_SPEED = 0;
-
- /**
- * The maximum allowed obscuring opacity by UID to propagate touches (0 <= x <= 1).
- * @hide
- */
- public static final float DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH = .8f;
-
- /**
* Prevent touches from being consumed by apps if these touches passed through a non-trusted
* window from a different UID and are considered unsafe.
*
@@ -1135,58 +1109,18 @@
}
/**
- * Gets the mouse pointer speed.
- * <p>
- * Only returns the permanent mouse pointer speed. Ignores any temporary pointer
- * speed set by {@link #tryPointerSpeed}.
- * </p>
- *
- * @param context The application context.
- * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
- * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
- *
- * @hide
- */
- public int getPointerSpeed(Context context) {
- return Settings.System.getInt(context.getContentResolver(),
- Settings.System.POINTER_SPEED, DEFAULT_POINTER_SPEED);
- }
-
- /**
- * Sets the mouse pointer speed.
- * <p>
- * Requires {@link android.Manifest.permission#WRITE_SETTINGS}.
- * </p>
- *
- * @param context The application context.
- * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
- * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public void setPointerSpeed(Context context, int speed) {
- if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
- throw new IllegalArgumentException("speed out of range");
- }
-
- Settings.System.putInt(context.getContentResolver(),
- Settings.System.POINTER_SPEED, speed);
- }
-
- /**
* Changes the mouse pointer speed temporarily, but does not save the setting.
* <p>
* Requires {@link android.Manifest.permission#SET_POINTER_SPEED}.
* </p>
*
- * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
- * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+ * @param speed The pointer speed as a value between {@link InputSettings#MIN_POINTER_SPEED} and
+ * {@link InputSettings#MAX_POINTER_SPEED}, or the default value {@link InputSettings#DEFAULT_POINTER_SPEED}.
*
* @hide
*/
public void tryPointerSpeed(int speed) {
- if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
+ if (speed < InputSettings.MIN_POINTER_SPEED || speed > InputSettings.MAX_POINTER_SPEED) {
throw new IllegalArgumentException("speed out of range");
}
@@ -1211,44 +1145,8 @@
*/
@FloatRange(from = 0, to = 1)
public float getMaximumObscuringOpacityForTouch() {
- return Settings.Global.getFloat(getContext().getContentResolver(),
- Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
- DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
- }
-
- /**
- * Sets the maximum allowed obscuring opacity by UID to propagate touches.
- *
- * <p>For certain window types (eg. SAWs), the decision of honoring {@link LayoutParams
- * #FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring opacity of the windows
- * above the touch-consuming window.
- *
- * <p>For a certain UID:
- * <ul>
- * <li>If it's the same as the UID of the touch-consuming window, allow it to propagate
- * the touch.
- * <li>Otherwise take all its windows of eligible window types above the touch-consuming
- * window, compute their combined obscuring opacity considering that {@code
- * opacity(A, B) = 1 - (1 - opacity(A))*(1 - opacity(B))}. If the computed value is
- * lesser than or equal to this setting and there are no other windows preventing the
- * touch, allow the UID to propagate the touch.
- * </ul>
- *
- * <p>This value should be between 0 (inclusive) and 1 (inclusive).
- *
- * @see #getMaximumObscuringOpacityForTouch()
- *
- * @hide
- */
- @TestApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
- public void setMaximumObscuringOpacityForTouch(@FloatRange(from = 0, to = 1) float opacity) {
- if (opacity < 0 || opacity > 1) {
- throw new IllegalArgumentException(
- "Maximum obscuring opacity for touch should be >= 0 and <= 1");
- }
- Settings.Global.putFloat(getContext().getContentResolver(),
- Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH, opacity);
+ Context context = ActivityThread.currentApplication();
+ return InputSettings.getMaximumObscuringOpacityForTouch(context);
}
/**
@@ -2145,26 +2043,6 @@
}
/**
- * Whether stylus has ever been used on device (false by default).
- * @hide
- */
- public boolean isStylusEverUsed(@NonNull Context context) {
- return Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.STYLUS_EVER_USED, 0) == 1;
- }
-
- /**
- * Set whether stylus has ever been used on device.
- * Should only ever be set to true once after stylus first usage.
- * @hide
- */
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
- public void setStylusEverUsed(@NonNull Context context, boolean stylusEverUsed) {
- Settings.Global.putInt(context.getContentResolver(),
- Settings.Global.STYLUS_EVER_USED, stylusEverUsed ? 1 : 0);
- }
-
- /**
* Whether there is a gesture-compatible touchpad connected to the device.
* @hide
*/
@@ -2174,200 +2052,6 @@
}
/**
- * Gets the touchpad pointer speed.
- *
- * The returned value only applies to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
- * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
- *
- * @hide
- */
- public int getTouchpadPointerSpeed(@NonNull Context context) {
- return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_POINTER_SPEED, DEFAULT_POINTER_SPEED,
- UserHandle.USER_CURRENT);
- }
-
- /**
- * Sets the touchpad pointer speed, and saves it in the settings.
- *
- * The new speed will only apply to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
- * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public void setTouchpadPointerSpeed(@NonNull Context context, int speed) {
- if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
- throw new IllegalArgumentException("speed out of range");
- }
-
- Settings.System.putIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_POINTER_SPEED, speed, UserHandle.USER_CURRENT);
- }
-
- /**
- * Returns true if the touchpad should use pointer acceleration.
- *
- * The returned value only applies to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @return Whether the touchpad should use pointer acceleration.
- *
- * @hide
- */
- public boolean useTouchpadPointerAcceleration(@NonNull Context context) {
- // TODO: obtain the actual behavior from the settings
- return true;
- }
-
- /**
- * Sets the pointer acceleration behavior for the touchpad.
- *
- * The new behavior is only applied to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @param enabled Will enable pointer acceleration if true, disable it if false
- *
- * @hide
- */
- public void setTouchpadPointerAcceleration(@NonNull Context context, boolean enabled) {
- // TODO: set the right setting
- }
-
- /**
- * Returns true if moving two fingers upwards on the touchpad should
- * scroll down, which is known as natural scrolling.
- *
- * The returned value only applies to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @return Whether the touchpad should use natural scrolling.
- *
- * @hide
- */
- public boolean useTouchpadNaturalScrolling(@NonNull Context context) {
- return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_NATURAL_SCROLLING, 0, UserHandle.USER_CURRENT) == 1;
- }
-
- /**
- * Sets the natural scroll behavior for the touchpad.
- *
- * If natural scrolling is enabled, moving two fingers upwards on the
- * touchpad will scroll down.
- *
- * @param context The application context.
- * @param enabled Will enable natural scroll if true, disable it if false
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public void setTouchpadNaturalScrolling(@NonNull Context context, boolean enabled) {
- Settings.System.putIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_NATURAL_SCROLLING, enabled ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- /**
- * Returns true if the touchpad should use tap to click.
- *
- * The returned value only applies to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @return Whether the touchpad should use tap to click.
- *
- * @hide
- */
- public boolean useTouchpadTapToClick(@NonNull Context context) {
- return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT) == 1;
- }
-
- /**
- * Sets the tap to click behavior for the touchpad.
- *
- * The new behavior is only applied to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @param enabled Will enable tap to click if true, disable it if false
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public void setTouchpadTapToClick(@NonNull Context context, boolean enabled) {
- Settings.System.putIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_TAP_TO_CLICK, enabled ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- /**
- * Returns true if the touchpad should use tap dragging.
- *
- * The returned value only applies to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @return Whether the touchpad should use tap dragging.
- *
- * @hide
- */
- public boolean useTouchpadTapDragging(@NonNull Context context) {
- // TODO: obtain the actual behavior from the settings
- return true;
- }
-
- /**
- * Sets the tap dragging behavior for the touchpad.
- *
- * The new behavior is only applied to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @param enabled Will enable tap dragging if true, disable it if false
- *
- * @hide
- */
- public void setTouchpadTapDragging(@NonNull Context context, boolean enabled) {
- // TODO: set the right setting
- }
-
- /**
- * Returns true if the touchpad should use the right click zone.
- *
- * The returned value only applies to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @return Whether the touchpad should use the right click zone.
- *
- * @hide
- */
- public boolean useTouchpadRightClickZone(@NonNull Context context) {
- return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, 0, UserHandle.USER_CURRENT) == 1;
- }
-
- /**
- * Sets the right click zone behavior for the touchpad.
- *
- * The new behavior is only applied to gesture-compatible touchpads.
- *
- * @param context The application context.
- * @param enabled Will enable the right click zone if true, disable it if false
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public void setTouchpadRightClickZone(@NonNull Context context, boolean enabled) {
- Settings.System.putIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, enabled ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- /**
* Registers a Keyboard backlight change listener to be notified about {@link
* KeyboardBacklightState} changes for connected keyboard devices.
*
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
new file mode 100644
index 0000000..cdf9ea5
--- /dev/null
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.Manifest;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+/**
+ * InputSettings encapsulates reading and writing settings related to input
+ *
+ * @hide
+ */
+@TestApi
+public class InputSettings {
+ /**
+ * Pointer Speed: The minimum (slowest) pointer speed (-7).
+ * @hide
+ */
+ public static final int MIN_POINTER_SPEED = -7;
+
+ /**
+ * Pointer Speed: The maximum (fastest) pointer speed (7).
+ * @hide
+ */
+ public static final int MAX_POINTER_SPEED = 7;
+
+ /**
+ * Pointer Speed: The default pointer speed (0).
+ * @hide
+ */
+ public static final int DEFAULT_POINTER_SPEED = 0;
+
+ /**
+ * The maximum allowed obscuring opacity by UID to propagate touches (0 <= x <= 1).
+ * @hide
+ */
+ public static final float DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH = .8f;
+
+
+ private InputSettings() {
+ }
+
+ /**
+ * Gets the mouse pointer speed.
+ * <p>
+ * Only returns the permanent mouse pointer speed. Ignores any temporary pointer
+ * speed set by {@link InputManager#tryPointerSpeed}.
+ * </p>
+ *
+ * @param context The application context.
+ * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+ * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+ *
+ * @hide
+ */
+ @SuppressLint("NonUserGetterCalled")
+ public static int getPointerSpeed(Context context) {
+ return Settings.System.getInt(context.getContentResolver(),
+ Settings.System.POINTER_SPEED, DEFAULT_POINTER_SPEED);
+ }
+
+ /**
+ * Sets the mouse pointer speed.
+ * <p>
+ * Requires {@link android.Manifest.permission#WRITE_SETTINGS}.
+ * </p>
+ *
+ * @param context The application context.
+ * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+ * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setPointerSpeed(Context context, int speed) {
+ if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
+ throw new IllegalArgumentException("speed out of range");
+ }
+
+ Settings.System.putInt(context.getContentResolver(),
+ Settings.System.POINTER_SPEED, speed);
+ }
+
+ /**
+ * Returns the maximum allowed obscuring opacity per UID to propagate touches.
+ *
+ * <p>For certain window types (e.g. {@link LayoutParams#TYPE_APPLICATION_OVERLAY}),
+ * the decision of honoring {@link LayoutParams#FLAG_NOT_TOUCHABLE} or not depends on
+ * the combined obscuring opacity of the windows above the touch-consuming window, per
+ * UID. Check documentation of {@link LayoutParams#FLAG_NOT_TOUCHABLE} for more details.
+ *
+ * <p>The value returned is between 0 (inclusive) and 1 (inclusive).
+ *
+ * @see LayoutParams#FLAG_NOT_TOUCHABLE
+ *
+ * @hide
+ */
+ @FloatRange(from = 0, to = 1)
+ public static float getMaximumObscuringOpacityForTouch(Context context) {
+ return Settings.Global.getFloat(context.getContentResolver(),
+ Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
+ DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
+ }
+
+ /**
+ * Sets the maximum allowed obscuring opacity by UID to propagate touches.
+ *
+ * <p>For certain window types (e.g. SAWs), the decision of honoring {@link LayoutParams
+ * #FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring opacity of the windows
+ * above the touch-consuming window.
+ *
+ * <p>For a certain UID:
+ * <ul>
+ * <li>If it's the same as the UID of the touch-consuming window, allow it to propagate
+ * the touch.
+ * <li>Otherwise take all its windows of eligible window types above the touch-consuming
+ * window, compute their combined obscuring opacity considering that {@code
+ * opacity(A, B) = 1 - (1 - opacity(A))*(1 - opacity(B))}. If the computed value is
+ * less than or equal to this setting and there are no other windows preventing the
+ * touch, allow the UID to propagate the touch.
+ * </ul>
+ *
+ * <p>This value should be between 0 (inclusive) and 1 (inclusive).
+ *
+ * @see #getMaximumObscuringOpacityForTouch(Context)
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public static void setMaximumObscuringOpacityForTouch(
+ @NonNull Context context,
+ @FloatRange(from = 0, to = 1) float opacity) {
+ if (opacity < 0 || opacity > 1) {
+ throw new IllegalArgumentException(
+ "Maximum obscuring opacity for touch should be >= 0 and <= 1");
+ }
+ Settings.Global.putFloat(context.getContentResolver(),
+ Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH, opacity);
+ }
+
+ /**
+ * Whether stylus has ever been used on device (false by default).
+ * @hide
+ */
+ public static boolean isStylusEverUsed(@NonNull Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.STYLUS_EVER_USED, 0) == 1;
+ }
+
+ /**
+ * Set whether stylus has ever been used on device.
+ * Should only ever be set to true once after stylus first usage.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public static void setStylusEverUsed(@NonNull Context context, boolean stylusEverUsed) {
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.STYLUS_EVER_USED, stylusEverUsed ? 1 : 0);
+ }
+
+
+ /**
+ * Gets the touchpad pointer speed.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+ * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+ *
+ * @hide
+ */
+ public static int getTouchpadPointerSpeed(@NonNull Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_POINTER_SPEED, DEFAULT_POINTER_SPEED,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Sets the touchpad pointer speed, and saves it in the settings.
+ *
+ * The new speed will only apply to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+ * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadPointerSpeed(@NonNull Context context, int speed) {
+ if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
+ throw new IllegalArgumentException("speed out of range");
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_POINTER_SPEED, speed, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Returns true if moving two fingers upwards on the touchpad should
+ * scroll down, which is known as natural scrolling.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return Whether the touchpad should use natural scrolling.
+ *
+ * @hide
+ */
+ public static boolean useTouchpadNaturalScrolling(@NonNull Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_NATURAL_SCROLLING, 0, UserHandle.USER_CURRENT) == 1;
+ }
+
+ /**
+ * Sets the natural scroll behavior for the touchpad.
+ *
+ * If natural scrolling is enabled, moving two fingers upwards on the
+ * touchpad will scroll down.
+ *
+ * @param context The application context.
+ * @param enabled Will enable natural scroll if true, disable it if false
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadNaturalScrolling(@NonNull Context context, boolean enabled) {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_NATURAL_SCROLLING, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Returns true if the touchpad should use tap to click.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return Whether the touchpad should use tap to click.
+ *
+ * @hide
+ */
+ public static boolean useTouchpadTapToClick(@NonNull Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT) == 1;
+ }
+
+ /**
+ * Sets the tap to click behavior for the touchpad.
+ *
+ * The new behavior is only applied to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @param enabled Will enable tap to click if true, disable it if false
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadTapToClick(@NonNull Context context, boolean enabled) {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_TAP_TO_CLICK, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Returns true if the touchpad should use the right click zone.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return Whether the touchpad should use the right click zone.
+ *
+ * @hide
+ */
+ public static boolean useTouchpadRightClickZone(@NonNull Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, 0, UserHandle.USER_CURRENT) == 1;
+ }
+
+ /**
+ * Sets the right click zone behavior for the touchpad.
+ *
+ * The new behavior is only applied to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @param enabled Will enable the right click zone if true, disable it if false
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadRightClickZone(@NonNull Context context, boolean enabled) {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+}
diff --git a/core/java/android/hardware/usb/DisplayPortAltModeInfo.java b/core/java/android/hardware/usb/DisplayPortAltModeInfo.java
index 7c62373..9da2f4c 100644
--- a/core/java/android/hardware/usb/DisplayPortAltModeInfo.java
+++ b/core/java/android/hardware/usb/DisplayPortAltModeInfo.java
@@ -79,7 +79,7 @@
* as one of its capabilities, however may not yet have entered DisplayPort Alt Mode or has been
* configured for data transmission.
*/
- public static final int DISPLAYPORT_ALT_MODE_STATUS_CAPABLE = 2;
+ public static final int DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED = 2;
/**
* Port Partners:
@@ -113,7 +113,7 @@
@IntDef(prefix = { "DISPLAYPORT_ALT_MODE_STATUS_" }, value = {
DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN,
DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE,
- DISPLAYPORT_ALT_MODE_STATUS_CAPABLE,
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
DISPLAYPORT_ALT_MODE_STATUS_ENABLED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -150,10 +150,6 @@
/**
* Returns the DisplayPort Alt Mode Status for a port partner acting as a sink.
*
- * @return {@link #DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN}
- * or {@link #DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE}
- * or {@link #DISPLAYPORT_ALT_MODE_STATUS_CAPABLE}
- * or {@link #DISPLAYPORT_ALT_MODE_STATUS_ENABLED}
*/
public @DisplayPortAltModeStatus int getPartnerSinkStatus() {
return mPartnerSinkStatus;
@@ -162,10 +158,6 @@
/**
* Returns the DisplayPort Alt Mode Status for the attached cable
*
- * @return {@link #DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN}
- * or {@link #DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE}
- * or {@link #DISPLAYPORT_ALT_MODE_STATUS_CAPABLE}
- * or {@link #DISPLAYPORT_ALT_MODE_STATUS_ENABLED}
*/
public @DisplayPortAltModeStatus int getCableStatus() {
return mCableStatus;
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 71ec1c6..889d3df 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1647,7 +1647,7 @@
/**
* Registers the given listener to listen for DisplayPort Alt Mode changes.
* <p>
- * If this method returns true, the caller should ensure to call
+ * If this method returns without Exceptions, the caller should ensure to call
* {@link #unregisterDisplayPortAltModeListener} when it no longer requires updates.
*
* @param executor Executor on which to run the listener.
@@ -1655,14 +1655,14 @@
* changes. See {@link #DisplayPortAltModeInfoListener} for listener
* details.
*
- * @return true on successful register, false on failed register due to listener already being
- * registered or an internal error.
+ * @throws IllegalStateException if listener has already been registered previously but not
+ * unregistered or an unexpected system failure occurs.
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
- public boolean registerDisplayPortAltModeInfoListener(
+ public void registerDisplayPortAltModeInfoListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull DisplayPortAltModeInfoListener listener) {
Objects.requireNonNull(executor, "registerDisplayPortAltModeInfoListener: "
@@ -1678,15 +1678,15 @@
if (mDisplayPortServiceListener == null) {
if (!registerDisplayPortAltModeEventsIfNeededLocked()) {
- return false;
+ throw new IllegalStateException("Unexpected failure registering service "
+ + "listener");
}
}
if (mDisplayPortListeners.containsKey(listener)) {
- return false;
+ throw new IllegalStateException("Listener has already been registered.");
}
mDisplayPortListeners.put(listener, executor);
- return true;
}
}
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 73dcb36..490b128 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -54,7 +54,7 @@
import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_OTHER;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
-import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED;
import android.Manifest;
@@ -807,8 +807,8 @@
return "Unknown";
case DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE:
return "Not Capable";
- case DISPLAYPORT_ALT_MODE_STATUS_CAPABLE:
- return "Capable";
+ case DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED:
+ return "Capable-Disabled";
case DISPLAYPORT_ALT_MODE_STATUS_ENABLED:
return "Enabled";
default:
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 8c13307..e1662b8 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -311,7 +311,7 @@
/**
* Indicates that the Type-C plug orientation cannot be
- * determined.
+ * determined because the connected state of the device is unknown.
*/
public static final int PLUG_STATE_UNKNOWN = 0;
@@ -600,13 +600,8 @@
}
/**
- * Returns the orientation state of the attached cable/adapter.
+ * Returns the plug state of the attached cable/adapter.
*
- * @return one of {@link #PLUG_STATE_UNKNOWN},
- * {@link #PLUG_STATE_UNPLUGGED},
- * {@link #PLUG_STATE_PLUGGED_ORIENTATION_UNKNOWN},
- * {@link #PLUG_STATE_PLUGGED_ORIENTATION_NORMAL},
- * {@link #PLUG_STATE_PLUGGED_ORIENTATION_FLIPPED},
*/
public @PlugState int getPlugState() {
return mPlugState;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 13d54ef..03c32d70 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2968,15 +2968,13 @@
"android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
/**
- * Intent that is broadcast when Low Power Standby is enabled or disabled.
+ * Intent that is broadcast when Low Power Standby policy is changed.
* This broadcast is only sent to registered receivers.
*
- * @see #getLowPowerStandbyPolicy
- * @see #setLowPowerStandbyPolicy
- * @hide
+ * @see #isExemptFromLowPowerStandby()
+ * @see #isAllowedInLowPowerStandby(int)
+ * @see #isAllowedInLowPowerStandby(String)
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY)
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LOW_POWER_STANDBY_POLICY_CHANGED =
"android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED";
@@ -2984,6 +2982,12 @@
/**
* Signals that wake-on-lan/wake-on-wlan is allowed in Low Power Standby.
*
+ * <p>If Low Power Standby is enabled ({@link #isLowPowerStandbyEnabled()}),
+ * wake-on-lan/wake-on-wlan may not be available while in standby.
+ * Use {@link #isAllowedInLowPowerStandby(String)} to determine whether the device allows this
+ * feature to be used during Low Power Standby with the currently active Low Power Standby
+ * policy.
+ *
* @see #isAllowedInLowPowerStandby(String)
*/
public static final String LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN =
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1df45d1..c60f558 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3612,7 +3612,7 @@
*
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
* {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
- * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}.
+ * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION.
*
* @param name the user's name
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
@@ -3686,7 +3686,7 @@
*
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
* {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
- * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}.
+ * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION.
*
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
* @return the {@link UserInfo} object for the created user.
@@ -3718,25 +3718,23 @@
*/
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
- public UserInfo createGuest(Context context) {
+ public @Nullable UserInfo createGuest(Context context) {
try {
final UserInfo guest = mService.createUserWithThrow(null, USER_TYPE_FULL_GUEST, 0);
- if (guest != null) {
- Settings.Secure.putStringForUser(context.getContentResolver(),
- Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
- if (UserManager.isGuestUserAllowEphemeralStateChange()) {
- // Mark guest as (changeably) ephemeral if REMOVE_GUEST_ON_EXIT is 1
- // This is done so that a user via a UI controller can choose to
- // make a guest as ephemeral or not.
- // Settings.Global.REMOVE_GUEST_ON_EXIT holds the choice on what the guest state
- // should be, with default being ephemeral.
- boolean resetGuestOnExit = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.REMOVE_GUEST_ON_EXIT, 1) == 1;
+ if (UserManager.isGuestUserAllowEphemeralStateChange()) {
+ // Mark guest as (changeably) ephemeral if REMOVE_GUEST_ON_EXIT is 1
+ // This is done so that a user via a UI controller can choose to
+ // make a guest as ephemeral or not.
+ // Settings.Global.REMOVE_GUEST_ON_EXIT holds the choice on what the guest state
+ // should be, with default being ephemeral.
+ boolean resetGuestOnExit = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT, 1) == 1;
- if (resetGuestOnExit && !guest.isEphemeral()) {
- setUserEphemeral(guest.id, true);
- }
+ if (resetGuestOnExit && !guest.isEphemeral()) {
+ setUserEphemeral(guest.id, true);
}
}
return guest;
@@ -3855,7 +3853,7 @@
*/
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
- public UserInfo createProfileForUser(String name, @NonNull String userType,
+ public @Nullable UserInfo createProfileForUser(String name, @NonNull String userType,
@UserInfoFlag int flags, @UserIdInt int userId) {
return createProfileForUser(name, userType, flags, userId, null);
}
@@ -3900,7 +3898,7 @@
*/
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
- public UserInfo createProfileForUserEvenWhenDisallowed(String name,
+ public @Nullable UserInfo createProfileForUserEvenWhenDisallowed(String name,
@NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId,
String[] disallowedPackages) {
try {
@@ -3931,11 +3929,9 @@
try {
final int parentUserId = mUserId;
final UserInfo profile = mService.createRestrictedProfileWithThrow(name, parentUserId);
- if (profile != null) {
- final UserHandle parentUserHandle = UserHandle.of(parentUserId);
- AccountManager.get(mContext).addSharedAccountsFromParentUser(parentUserHandle,
- UserHandle.of(profile.id));
- }
+ final UserHandle parentUserHandle = UserHandle.of(parentUserId);
+ AccountManager.get(mContext).addSharedAccountsFromParentUser(parentUserHandle,
+ UserHandle.of(profile.id));
return profile;
} catch (ServiceSpecificException e) {
return null;
diff --git a/core/java/android/provider/DeviceConfigInitializer.java b/core/java/android/provider/DeviceConfigInitializer.java
new file mode 100644
index 0000000..d60449f
--- /dev/null
+++ b/core/java/android/provider/DeviceConfigInitializer.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * Class that will hold an instance of {@link DeviceConfigServiceManager}
+ * which is used by {@link DeviceConfig} to retrieve an instance of the service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class DeviceConfigInitializer {
+ private static DeviceConfigServiceManager sDeviceConfigServiceManager;
+
+ private static final Object sLock = new Object();
+
+ private DeviceConfigInitializer() {
+ // fully static class
+ }
+
+ /**
+ * Setter for {@link DeviceConfigServiceManager}. Should be called only once.
+ *
+ */
+ public static void setDeviceConfigServiceManager(
+ @NonNull DeviceConfigServiceManager serviceManager) {
+ synchronized (sLock) {
+ if (sDeviceConfigServiceManager != null) {
+ throw new IllegalStateException("setDeviceConfigServiceManager called twice!");
+ }
+ Objects.requireNonNull(serviceManager, "serviceManager must not be null");
+
+ sDeviceConfigServiceManager = serviceManager;
+ }
+ }
+
+ /**
+ * Getter for {@link DeviceConfigServiceManager}.
+ *
+ */
+ @Nullable
+ public static DeviceConfigServiceManager getDeviceConfigServiceManager() {
+ synchronized (sLock) {
+ return sDeviceConfigServiceManager;
+ }
+ }
+}
diff --git a/core/java/android/provider/DeviceConfigServiceManager.java b/core/java/android/provider/DeviceConfigServiceManager.java
new file mode 100644
index 0000000..c362c37
--- /dev/null
+++ b/core/java/android/provider/DeviceConfigServiceManager.java
@@ -0,0 +1,141 @@
+/*
+ * 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.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Service Manager for the {@code android.provider.DeviceConfig} service.
+ *
+ * <p>Used to be able to get an instance of the service in places that don't have access to a
+ * {@code Context}
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class DeviceConfigServiceManager {
+
+ /**
+ * @hide
+ */
+ public DeviceConfigServiceManager() {
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Register a system server binding object for a service.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void register(@NonNull IBinder service) {
+ ServiceManager.addService(mServiceName, service);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it returns null.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @Nullable
+ public IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow}.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor.
+ *
+ * @param name the name of the binder service that cannot be found.
+ *
+ * @hide
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the device config service that
+ * is updatable via mainline.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public ServiceRegisterer getDeviceConfigUpdatableServiceRegisterer() {
+ return new ServiceRegisterer("device_config_updatable");
+ }
+}
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index cdcd6591..a8fcf86 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -166,7 +166,7 @@
PixelFormat.TRANSPARENT);
final SurfaceControlViewHost host = new SurfaceControlViewHost(this, getDisplay(),
- hostInputToken);
+ hostInputToken, "InlineSuggestionRenderService");
host.setView(suggestionRoot, lp);
// Set the suggestion view to be non-focusable so that if its background is set to a
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
index 52c8ec3..f844423 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -125,7 +125,7 @@
final Context windowContext = createWindowContext(display,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, /*options=*/ null);
SurfaceControlViewHost surfaceControlViewHost =
- new SurfaceControlViewHost(windowContext, display, hostToken);
+ new SurfaceControlViewHost(windowContext, display, hostToken, "GameSessionService");
gameSession.attach(
gameSessionController,
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 3807685..9696dbc 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -51,8 +51,17 @@
/** @hide */
@StringDef (prefix = { "KEY_" }, value = {
- KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA,
- KEY_TEXT_REPLIES, KEY_USER_SENTIMENT, KEY_IMPORTANCE_PROPOSAL, KEY_SENSITIVE_CONTENT
+ KEY_PEOPLE,
+ KEY_SNOOZE_CRITERIA,
+ KEY_GROUP_KEY,
+ KEY_USER_SENTIMENT,
+ KEY_CONTEXTUAL_ACTIONS,
+ KEY_TEXT_REPLIES,
+ KEY_IMPORTANCE,
+ KEY_IMPORTANCE_PROPOSAL,
+ KEY_SENSITIVE_CONTENT,
+ KEY_RANKING_SCORE,
+ KEY_NOT_CONVERSATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface Keys {}
@@ -65,6 +74,7 @@
*/
@SystemApi
public static final String KEY_PEOPLE = "key_people";
+
/**
* Parcelable {@code ArrayList} of {@link SnoozeCriterion}. These criteria may be visible to
* users. If a user chooses to snooze a notification until one of these criterion, the
@@ -72,6 +82,7 @@
* {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
*/
public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
+
/**
* Data type: String. Used to change what {@link Notification#getGroup() group} a notification
* belongs to.
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index b384b66..37a91e7 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -58,6 +58,7 @@
void onSuggestedReplySent(String key, in CharSequence reply, int source);
void onActionClicked(String key, in Notification.Action action, int source);
void onNotificationClicked(String key);
+ // @deprecated changing allowed adjustments is no longer supported.
void onAllowedAdjustmentsChanged();
void onNotificationFeedbackReceived(String key, in NotificationRankingUpdate update, in Bundle feedback);
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index a38ef96..76889df 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -293,7 +293,10 @@
* their notifications the assistant can modify.
* <p> Query {@link NotificationManager#getAllowedAssistantAdjustments()} to see what
* {@link Adjustment adjustments} you are currently allowed to make.</p>
+ *
+ * @deprecated changing allowed adjustments is no longer supported.
*/
+ @Deprecated
public void onAllowedAdjustmentsChanged() {
}
diff --git a/core/java/android/service/search/ISearchUiService.aidl b/core/java/android/service/search/ISearchUiService.aidl
index bc6d421..f59347f 100644
--- a/core/java/android/service/search/ISearchUiService.aidl
+++ b/core/java/android/service/search/ISearchUiService.aidl
@@ -39,8 +39,6 @@
void onRegisterEmptyQueryResultUpdateCallback (in SearchSessionId sessionId, in ISearchCallback callback);
- void onRequestEmptyQueryResultUpdate(in SearchSessionId sessionId);
-
void onUnregisterEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
void onDestroy(in SearchSessionId sessionId);
diff --git a/core/java/android/service/search/SearchUiService.java b/core/java/android/service/search/SearchUiService.java
index 55a96fa..8d05b80 100644
--- a/core/java/android/service/search/SearchUiService.java
+++ b/core/java/android/service/search/SearchUiService.java
@@ -112,12 +112,6 @@
}
@Override
- public void onRequestEmptyQueryResultUpdate(SearchSessionId sessionId) {
- mHandler.sendMessage(obtainMessage(SearchUiService::doRequestEmptyQueryResultUpdate,
- SearchUiService.this, sessionId));
- }
-
- @Override
public void onUnregisterEmptyQueryResultUpdateCallback(SearchSessionId sessionId,
ISearchCallback callback) {
mHandler.sendMessage(
@@ -220,24 +214,6 @@
@MainThread
public void onStartUpdateEmptyQueryResult() {}
- private void doRequestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {
- // Just an optimization, if there are no callbacks, then don't bother notifying the service
- final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
- sessionId);
- if (callbacks != null && !callbacks.isEmpty()) {
- onRequestEmptyQueryResultUpdate(sessionId);
- }
- }
-
- /**
- * Called by a client to request empty query search target result for zero state. This method
- * is only called if there are one or more empty query result update callbacks registered.
- *
- * @see #updateEmptyQueryResult(SearchSessionId, List)
- */
- @MainThread
- public void onRequestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {}
-
private void doUnregisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
@NonNull ISearchCallback callback) {
final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index 9292e96..59e3a5e 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -275,7 +275,7 @@
mHostInputToken, mTransferTouchListener);
contentHolder.addView(mContentContainer);
mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
- mHostInputToken);
+ mHostInputToken, "RemoteSelectionToolbar");
mSurfaceControlViewHost.setView(contentHolder, mPopupWidth, mPopupHeight);
}
if (mSurfacePackage == null) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 23513fad..120e871 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -45,7 +45,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
-import android.content.res.TypedArray;
import android.graphics.BLASTBufferQueue;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -1153,11 +1152,6 @@
mLayout.token = mWindowToken;
if (!mCreated) {
- // Retrieve watch round info
- TypedArray windowStyle = obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- windowStyle.recycle();
-
// Add window
mLayout.type = mIWallpaperEngine.mWindowType;
mLayout.gravity = Gravity.START|Gravity.TOP;
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index ad3ad7a..cc64c45 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -67,12 +67,15 @@
* given recognizerIntent. For more information see {@link #startListening} and
* {@link RecognizerIntent}.
*/
- void checkRecognitionSupport(in Intent recognizerIntent, in IRecognitionSupportCallback listener);
+ void checkRecognitionSupport(
+ in Intent recognizerIntent,
+ in AttributionSource attributionSource,
+ in IRecognitionSupportCallback listener);
/**
* Requests RecognitionService to download the support for the given recognizerIntent. For more
* information see {@link #checkRecognitionSupport}, {@link #startListening} and
* {@link RecognizerIntent}.
*/
- void triggerModelDownload(in Intent recognizerIntent);
+ void triggerModelDownload(in Intent recognizerIntent, in AttributionSource attributionSource);
}
diff --git a/core/java/android/speech/RecognitionPart.aidl b/core/java/android/speech/RecognitionPart.aidl
new file mode 100644
index 0000000..f7754a2
--- /dev/null
+++ b/core/java/android/speech/RecognitionPart.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech;
+
+parcelable RecognitionPart;
diff --git a/core/java/android/speech/RecognitionPart.java b/core/java/android/speech/RecognitionPart.java
new file mode 100644
index 0000000..e551cdc
--- /dev/null
+++ b/core/java/android/speech/RecognitionPart.java
@@ -0,0 +1,488 @@
+/*
+ * 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.speech;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Info about a single recognition part.
+ *
+ * <p> A recognition part represents a recognized word or character, as well as any potential
+ * adjacent punctuation, that is returned by the {@link SpeechRecognizer}.
+ *
+ * <p> Each recognition part is described with a {@link String} denoting the raw text.
+ * Additionally, if formatting is enabled with {@link RecognizerIntent#EXTRA_ENABLE_FORMATTING},
+ * another {@link String} representation exists denoting the formatted text.
+ *
+ * <p> If the timestamps are requested with {@link RecognizerIntent#EXTRA_REQUEST_WORD_TIMING}, each
+ * recognition part will contain a value representing the offset of the beginning of this part from
+ * the start of the recognition session in milliseconds.
+ *
+ * <p> If the confidence levels are requested with
+ * {@link RecognizerIntent#EXTRA_REQUEST_WORD_CONFIDENCE}, each recognition part will contain
+ * a value describing the level of recognition confidence.
+ */
+@DataClass(
+ genBuilder = true,
+ genEqualsHashCode = true,
+ genHiddenConstDefs = true,
+ genToString = true)
+public final class RecognitionPart implements Parcelable {
+
+ /** Confidence level not requested. */
+ public static final int CONFIDENCE_LEVEL_UNKNOWN = 0;
+
+ /** Lowest level of confidence out of five levels. */
+ public static final int CONFIDENCE_LEVEL_LOW = 1;
+
+ /** Second-lowest level of confidence out of five levels. */
+ public static final int CONFIDENCE_LEVEL_LOW_MEDIUM = 2;
+
+ /** Medium level of confidence out of five levels. */
+ public static final int CONFIDENCE_LEVEL_MEDIUM = 3;
+
+ /** Second-highest level of confidence out of five levels. */
+ public static final int CONFIDENCE_LEVEL_MEDIUM_HIGH = 4;
+
+ /** Highest level of confidence out of five levels. */
+ public static final int CONFIDENCE_LEVEL_HIGH = 5;
+
+ /** The {@code non-null} raw text version of the recognized part of the result. */
+ @NonNull
+ private final String mRawText;
+
+ /**
+ * The formatted text version of the recognized part of the result. If formatting is enabled
+ * with {@link RecognizerIntent#EXTRA_ENABLE_FORMATTING}, it has a {@code non-null} value.
+ *
+ * <p> Otherwise, it should be {@code null} by default.
+ */
+ @Nullable
+ private final String mFormattedText;
+ private static String defaultFormattedText() {
+ return null;
+ }
+
+ /**
+ * Non-negative offset of the beginning of this part from
+ * the start of the recognition session in milliseconds
+ * if requested with {@link RecognizerIntent#EXTRA_REQUEST_WORD_TIMING}.
+ *
+ * <p> Otherwise, this should equal 0.
+ */
+ private final long mTimestampMillis;
+ private static long defaultTimestampMillis() {
+ return 0;
+ }
+
+ /**
+ * The level of confidence for this part if requested
+ * with {@link RecognizerIntent#EXTRA_REQUEST_WORD_CONFIDENCE}.
+ *
+ * <p> Otherwise, this should equal {@link #CONFIDENCE_LEVEL_UNKNOWN}.
+ */
+ @ConfidenceLevel
+ private final int mConfidenceLevel;
+ @ConfidenceLevel
+ private static int defaultConfidenceLevel() {
+ return CONFIDENCE_LEVEL_UNKNOWN;
+ }
+
+ private void onConstructed() {
+ Preconditions.checkArgumentNonnegative(mTimestampMillis,
+ "The timestamp must be non-negative.");
+ }
+
+ @DataClass.Suppress("setFormattedText")
+ abstract static class BaseBuilder {
+ /**
+ * The formatted text version of the recognized part of the result. If formatting is enabled
+ * with {@link RecognizerIntent#EXTRA_ENABLE_FORMATTING}, it has a {@code non-null} value.
+ *
+ * <p> Otherwise, it should be {@code null} by default.
+ */
+ @NonNull
+ public Builder setFormattedText(@NonNull String value) {
+ // Method explicitly defined, so that the argument can be checked for non-null.
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, value);
+
+ final Builder builder = (Builder) this;
+ builder.checkNotUsed();
+ builder.mBuilderFieldsSet |= 0x2;
+ builder.mFormattedText = value;
+ return builder;
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/speech/RecognitionPart.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @android.annotation.IntDef(prefix = "CONFIDENCE_LEVEL_", value = {
+ CONFIDENCE_LEVEL_UNKNOWN,
+ CONFIDENCE_LEVEL_LOW,
+ CONFIDENCE_LEVEL_LOW_MEDIUM,
+ CONFIDENCE_LEVEL_MEDIUM,
+ CONFIDENCE_LEVEL_MEDIUM_HIGH,
+ CONFIDENCE_LEVEL_HIGH
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface ConfidenceLevel {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String confidenceLevelToString(@ConfidenceLevel int value) {
+ switch (value) {
+ case CONFIDENCE_LEVEL_UNKNOWN:
+ return "CONFIDENCE_LEVEL_UNKNOWN";
+ case CONFIDENCE_LEVEL_LOW:
+ return "CONFIDENCE_LEVEL_LOW";
+ case CONFIDENCE_LEVEL_LOW_MEDIUM:
+ return "CONFIDENCE_LEVEL_LOW_MEDIUM";
+ case CONFIDENCE_LEVEL_MEDIUM:
+ return "CONFIDENCE_LEVEL_MEDIUM";
+ case CONFIDENCE_LEVEL_MEDIUM_HIGH:
+ return "CONFIDENCE_LEVEL_MEDIUM_HIGH";
+ case CONFIDENCE_LEVEL_HIGH:
+ return "CONFIDENCE_LEVEL_HIGH";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ RecognitionPart(
+ @NonNull String rawText,
+ @Nullable String formattedText,
+ long timestampMillis,
+ @ConfidenceLevel int confidenceLevel) {
+ this.mRawText = rawText;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mRawText);
+ this.mFormattedText = formattedText;
+ this.mTimestampMillis = timestampMillis;
+ this.mConfidenceLevel = confidenceLevel;
+
+ if (!(mConfidenceLevel == CONFIDENCE_LEVEL_UNKNOWN)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_LOW)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_LOW_MEDIUM)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_MEDIUM)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_MEDIUM_HIGH)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_HIGH)) {
+ throw new java.lang.IllegalArgumentException(
+ "confidenceLevel was " + mConfidenceLevel + " but must be one of: "
+ + "CONFIDENCE_LEVEL_UNKNOWN(" + CONFIDENCE_LEVEL_UNKNOWN + "), "
+ + "CONFIDENCE_LEVEL_LOW(" + CONFIDENCE_LEVEL_LOW + "), "
+ + "CONFIDENCE_LEVEL_LOW_MEDIUM(" + CONFIDENCE_LEVEL_LOW_MEDIUM + "), "
+ + "CONFIDENCE_LEVEL_MEDIUM(" + CONFIDENCE_LEVEL_MEDIUM + "), "
+ + "CONFIDENCE_LEVEL_MEDIUM_HIGH(" + CONFIDENCE_LEVEL_MEDIUM_HIGH + "), "
+ + "CONFIDENCE_LEVEL_HIGH(" + CONFIDENCE_LEVEL_HIGH + ")");
+ }
+
+
+ onConstructed();
+ }
+
+ /**
+ * The {@code non-null} raw text version of the recognized part of the result.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getRawText() {
+ return mRawText;
+ }
+
+ /**
+ * The formatted text version of the recognized part of the result. If formatting is enabled
+ * with {@link RecognizerIntent#EXTRA_ENABLE_FORMATTING}, it has a {@code non-null} value.
+ *
+ * <p> Otherwise, it should be {@code null} by default.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getFormattedText() {
+ return mFormattedText;
+ }
+
+ /**
+ * Non-negative offset of the beginning of this part from
+ * the start of the recognition session in milliseconds
+ * if requested with {@link RecognizerIntent#EXTRA_REQUEST_WORD_TIMING}.
+ *
+ * <p> Otherwise, this should equal 0.
+ */
+ @DataClass.Generated.Member
+ public long getTimestampMillis() {
+ return mTimestampMillis;
+ }
+
+ /**
+ * The level of confidence for this part if requested
+ * with {@link RecognizerIntent#EXTRA_REQUEST_WORD_CONFIDENCE}.
+ *
+ * <p> Otherwise, this should equal {@link #CONFIDENCE_LEVEL_UNKNOWN}.
+ */
+ @DataClass.Generated.Member
+ public @ConfidenceLevel int getConfidenceLevel() {
+ return mConfidenceLevel;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "RecognitionPart { " +
+ "rawText = " + mRawText + ", " +
+ "formattedText = " + mFormattedText + ", " +
+ "timestampMillis = " + mTimestampMillis + ", " +
+ "confidenceLevel = " + confidenceLevelToString(mConfidenceLevel) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(RecognitionPart other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ RecognitionPart that = (RecognitionPart) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mRawText, that.mRawText)
+ && java.util.Objects.equals(mFormattedText, that.mFormattedText)
+ && mTimestampMillis == that.mTimestampMillis
+ && mConfidenceLevel == that.mConfidenceLevel;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mRawText);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFormattedText);
+ _hash = 31 * _hash + Long.hashCode(mTimestampMillis);
+ _hash = 31 * _hash + mConfidenceLevel;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mFormattedText != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeString(mRawText);
+ if (mFormattedText != null) dest.writeString(mFormattedText);
+ dest.writeLong(mTimestampMillis);
+ dest.writeInt(mConfidenceLevel);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ RecognitionPart(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String rawText = in.readString();
+ String formattedText = (flg & 0x2) == 0 ? null : in.readString();
+ long timestampMillis = in.readLong();
+ int confidenceLevel = in.readInt();
+
+ this.mRawText = rawText;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mRawText);
+ this.mFormattedText = formattedText;
+ this.mTimestampMillis = timestampMillis;
+ this.mConfidenceLevel = confidenceLevel;
+
+ if (!(mConfidenceLevel == CONFIDENCE_LEVEL_UNKNOWN)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_LOW)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_LOW_MEDIUM)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_MEDIUM)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_MEDIUM_HIGH)
+ && !(mConfidenceLevel == CONFIDENCE_LEVEL_HIGH)) {
+ throw new java.lang.IllegalArgumentException(
+ "confidenceLevel was " + mConfidenceLevel + " but must be one of: "
+ + "CONFIDENCE_LEVEL_UNKNOWN(" + CONFIDENCE_LEVEL_UNKNOWN + "), "
+ + "CONFIDENCE_LEVEL_LOW(" + CONFIDENCE_LEVEL_LOW + "), "
+ + "CONFIDENCE_LEVEL_LOW_MEDIUM(" + CONFIDENCE_LEVEL_LOW_MEDIUM + "), "
+ + "CONFIDENCE_LEVEL_MEDIUM(" + CONFIDENCE_LEVEL_MEDIUM + "), "
+ + "CONFIDENCE_LEVEL_MEDIUM_HIGH(" + CONFIDENCE_LEVEL_MEDIUM_HIGH + "), "
+ + "CONFIDENCE_LEVEL_HIGH(" + CONFIDENCE_LEVEL_HIGH + ")");
+ }
+
+
+ onConstructed();
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<RecognitionPart> CREATOR
+ = new Parcelable.Creator<RecognitionPart>() {
+ @Override
+ public RecognitionPart[] newArray(int size) {
+ return new RecognitionPart[size];
+ }
+
+ @Override
+ public RecognitionPart createFromParcel(@NonNull android.os.Parcel in) {
+ return new RecognitionPart(in);
+ }
+ };
+
+ /**
+ * A builder for {@link RecognitionPart}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder extends BaseBuilder {
+
+ private @NonNull String mRawText;
+ private @Nullable String mFormattedText;
+ private long mTimestampMillis;
+ private @ConfidenceLevel int mConfidenceLevel;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param rawText
+ * The {@code non-null} raw text version of the recognized part of the result.
+ */
+ public Builder(
+ @NonNull String rawText) {
+ mRawText = rawText;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mRawText);
+ }
+
+ /**
+ * The {@code non-null} raw text version of the recognized part of the result.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRawText(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRawText = value;
+ return this;
+ }
+
+ /**
+ * Non-negative offset of the beginning of this part from
+ * the start of the recognition session in milliseconds
+ * if requested with {@link RecognizerIntent#EXTRA_REQUEST_WORD_TIMING}.
+ *
+ * <p> Otherwise, this should equal 0.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTimestampMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTimestampMillis = value;
+ return this;
+ }
+
+ /**
+ * The level of confidence for this part if requested
+ * with {@link RecognizerIntent#EXTRA_REQUEST_WORD_CONFIDENCE}.
+ *
+ * <p> Otherwise, this should equal {@link #CONFIDENCE_LEVEL_UNKNOWN}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setConfidenceLevel(@ConfidenceLevel int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mConfidenceLevel = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull RecognitionPart build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mFormattedText = defaultFormattedText();
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTimestampMillis = defaultTimestampMillis();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mConfidenceLevel = defaultConfidenceLevel();
+ }
+ RecognitionPart o = new RecognitionPart(
+ mRawText,
+ mFormattedText,
+ mTimestampMillis,
+ mConfidenceLevel);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1676294616910L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/speech/RecognitionPart.java",
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_UNKNOWN\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\nprivate final @android.annotation.NonNull java.lang.String mRawText\nprivate final @android.annotation.Nullable java.lang.String mFormattedText\nprivate final long mTimestampMillis\nprivate final @android.speech.RecognitionPart.ConfidenceLevel int mConfidenceLevel\nprivate static java.lang.String defaultFormattedText()\nprivate static long defaultTimestampMillis()\nprivate static @android.speech.RecognitionPart.ConfidenceLevel int defaultConfidenceLevel()\nprivate void onConstructed()\nclass RecognitionPart extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.speech.RecognitionPart.Builder setFormattedText(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genToString=true)\npublic @android.annotation.NonNull android.speech.RecognitionPart.Builder setFormattedText(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 1cc772a..0b0b3b56 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -110,13 +110,14 @@
dispatchClearCallback((IRecognitionListener) msg.obj);
break;
case MSG_CHECK_RECOGNITION_SUPPORT:
- Pair<Intent, IRecognitionSupportCallback> intentAndListener =
- (Pair<Intent, IRecognitionSupportCallback>) msg.obj;
+ CheckRecognitionSupportArgs checkArgs = (CheckRecognitionSupportArgs) msg.obj;
dispatchCheckRecognitionSupport(
- intentAndListener.first, intentAndListener.second);
+ checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
break;
case MSG_TRIGGER_MODEL_DOWNLOAD:
- dispatchTriggerModelDownload((Intent) msg.obj);
+ Pair<Intent, AttributionSource> params =
+ (Pair<Intent, AttributionSource>) msg.obj;
+ dispatchTriggerModelDownload(params.first, params.second);
break;
}
}
@@ -142,6 +143,10 @@
if (preflightPermissionCheckPassed) {
currentCallback = new Callback(listener, attributionSource);
sessionState = new SessionState(currentCallback);
+ mSessions.put(listener.asBinder(), sessionState);
+ if (DBG) {
+ Log.d(TAG, "Added a new session to the map, pending permission checks");
+ }
RecognitionService.this.onStartListening(intent, currentCallback);
}
@@ -151,16 +156,12 @@
if (preflightPermissionCheckPassed) {
// If start listening was attempted, cancel the callback.
RecognitionService.this.onCancel(currentCallback);
+ mSessions.remove(listener.asBinder());
finishDataDelivery(sessionState);
sessionState.reset();
}
Log.i(TAG, "#startListening received from a caller "
+ "without permission " + Manifest.permission.RECORD_AUDIO + ".");
- } else {
- if (DBG) {
- Log.d(TAG, "Added a new session to the map.");
- }
- mSessions.put(listener.asBinder(), sessionState);
}
} else {
listener.onError(SpeechRecognizer.ERROR_CLIENT);
@@ -211,12 +212,18 @@
}
private void dispatchCheckRecognitionSupport(
- Intent intent, IRecognitionSupportCallback callback) {
- RecognitionService.this.onCheckRecognitionSupport(intent, new SupportCallback(callback));
+ Intent intent, IRecognitionSupportCallback callback,
+ AttributionSource attributionSource) {
+ RecognitionService.this.onCheckRecognitionSupport(
+ intent,
+ attributionSource,
+ new SupportCallback(callback));
}
- private void dispatchTriggerModelDownload(Intent intent) {
- RecognitionService.this.onTriggerModelDownload(intent);
+ private void dispatchTriggerModelDownload(
+ Intent intent,
+ AttributionSource attributionSource) {
+ RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
}
private static class StartListeningArgs {
@@ -233,6 +240,21 @@
}
}
+ private static class CheckRecognitionSupportArgs {
+ public final Intent mIntent;
+ public final IRecognitionSupportCallback callback;
+ public final AttributionSource mAttributionSource;
+
+ private CheckRecognitionSupportArgs(
+ Intent intent,
+ IRecognitionSupportCallback callback,
+ AttributionSource attributionSource) {
+ this.mIntent = intent;
+ this.callback = callback;
+ this.mAttributionSource = attributionSource;
+ }
+ }
+
/**
* Notifies the service that it should start listening for speech.
*
@@ -298,6 +320,26 @@
}
/**
+ * Queries the service on whether it would support a {@link #onStartListening(Intent, Callback)}
+ * for the same {@code recognizerIntent}.
+ *
+ * <p>The service will notify the caller about the level of support or error via
+ * {@link SupportCallback}.
+ *
+ * <p>If the service does not offer the support check it will notify the caller with
+ * {@link SpeechRecognizer#ERROR_CANNOT_CHECK_SUPPORT}.
+ *
+ * <p>Provides the calling AttributionSource to the service implementation so that permissions
+ * and bandwidth could be correctly blamed.</p>
+ */
+ public void onCheckRecognitionSupport(
+ @NonNull Intent recognizerIntent,
+ @NonNull AttributionSource attributionSource,
+ @NonNull SupportCallback supportCallback) {
+ onCheckRecognitionSupport(recognizerIntent, supportCallback);
+ }
+
+ /**
* Requests the download of the recognizer support for {@code recognizerIntent}.
*/
public void onTriggerModelDownload(@NonNull Intent recognizerIntent) {
@@ -306,6 +348,18 @@
}
}
+ /**
+ * Requests the download of the recognizer support for {@code recognizerIntent}.
+ *
+ * <p>Provides the calling AttributionSource to the service implementation so that permissions
+ * and bandwidth could be correctly blamed.</p>
+ */
+ public void onTriggerModelDownload(
+ @NonNull Intent recognizerIntent,
+ @NonNull AttributionSource attributionSource) {
+ onTriggerModelDownload(recognizerIntent);
+ }
+
@Override
@SuppressLint("MissingNullability")
public Context createContext(@NonNull ContextParams contextParams) {
@@ -524,7 +578,8 @@
public static class SupportCallback {
private final IRecognitionSupportCallback mCallback;
- private SupportCallback(IRecognitionSupportCallback callback) {
+ private SupportCallback(
+ IRecognitionSupportCallback callback) {
this.mCallback = callback;
}
@@ -596,22 +651,27 @@
@Override
public void checkRecognitionSupport(
- Intent recognizerIntent, IRecognitionSupportCallback callback) {
+ Intent recognizerIntent,
+ @NonNull AttributionSource attributionSource,
+ IRecognitionSupportCallback callback) {
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(service.mHandler, MSG_CHECK_RECOGNITION_SUPPORT,
- Pair.create(recognizerIntent, callback)));
+ new CheckRecognitionSupportArgs(
+ recognizerIntent, callback, attributionSource)));
}
}
@Override
- public void triggerModelDownload(Intent recognizerIntent) {
+ public void triggerModelDownload(
+ Intent recognizerIntent, @NonNull AttributionSource attributionSource) {
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(
- service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
+ service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
+ Pair.create(recognizerIntent, attributionSource)));
}
}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index cd18c19..7127fa1 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -558,4 +558,18 @@
* @see #EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS
*/
public static final String EXTRA_SEGMENTED_SESSION = "android.speech.extra.SEGMENTED_SESSION";
+
+ /**
+ * Optional boolean indicating whether the recognizer should return the timestamp
+ * of each word in the final recognition results.
+ */
+ public static final String EXTRA_REQUEST_WORD_TIMING =
+ "android.speech.extra.REQUEST_WORD_TIMING";
+
+ /**
+ * Optional boolean indicating whether the recognizer should return the confidence
+ * level of each word in the final recognition results.
+ */
+ public static final String EXTRA_REQUEST_WORD_CONFIDENCE =
+ "android.speech.extra.REQUEST_WORD_CONFIDENCE";
}
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index bcb8005..9c46e55 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -113,6 +113,17 @@
public static final String RESULTS_ALTERNATIVES = "results_alternatives";
/**
+ * Key used to receive an ArrayList<{@link RecognitionPart}> object from the
+ * {@link Bundle} passed to the {@link RecognitionListener#onResults(Bundle)} and
+ * {@link RecognitionListener#onSegmentResults(Bundle)} methods.
+ *
+ * <p> A single {@link SpeechRecognizer} result is represented as a {@link String}. Each word of
+ * the resulting String, as well as any potential adjacent punctuation, is represented by a
+ * {@link RecognitionPart} item from the ArrayList retrieved by this key.
+ */
+ public static final String RECOGNITION_PARTS = "recognition_parts";
+
+ /**
* The reason speech recognition failed.
*
* @hide
@@ -652,6 +663,7 @@
try {
mService.checkRecognitionSupport(
recognizerIntent,
+ mContext.getAttributionSource(),
new InternalSupportCallback(callbackExecutor, recognitionSupportCallback));
if (DBG) Log.d(TAG, "service support command succeeded");
} catch (final RemoteException e) {
@@ -665,7 +677,7 @@
return;
}
try {
- mService.triggerModelDownload(recognizerIntent);
+ mService.triggerModelDownload(recognizerIntent, mContext.getAttributionSource());
} catch (final RemoteException e) {
Log.e(TAG, "downloadModel() failed", e);
mListener.onError(ERROR_CLIENT);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 680e8f7..a746dc6 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -96,12 +96,6 @@
public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
/**
- * Enable new shortcut list UI
- * @hide
- */
- public static final String SETTINGS_NEW_KEYBOARD_SHORTCUT = "settings_new_keyboard_shortcut";
-
- /**
* Enable new modifier key settings UI
* @hide
*/
@@ -227,7 +221,6 @@
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");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
@@ -255,7 +248,6 @@
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
- PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 3dc79cf..4a83bbe 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -367,6 +367,14 @@
}
/**
+ * Check if the sourced looper and the current looper are same.
+ * @hide
+ */
+ boolean isTheLooperSame(Looper looper) {
+ return mLooper == looper;
+ }
+
+ /**
* The amount of time, in milliseconds, between each frame of the animation.
* <p>
* This is a requested time that the animation will attempt to honor, but the actual delay
@@ -1165,7 +1173,8 @@
*
* @param data The payload which includes frame information. Divide nanosecond values by
* {@code 1000000} to convert it to the {@link SystemClock#uptimeMillis()}
- * time base.
+ * time base. {@code data} is not valid outside of {@code onVsync} and should
+ * not be accessed outside the callback.
* @see FrameCallback#doFrame
**/
void onVsync(@NonNull FrameData data);
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 6b1499f..20be9d6 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -56,6 +56,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides information about the size and density of a logical display.
@@ -111,6 +113,8 @@
private int mCachedAppWidthCompat;
private int mCachedAppHeightCompat;
+ private ArrayList<HdrSdrRatioListenerWrapper> mHdrSdrRatioListeners = new ArrayList<>();
+
/**
* The default Display id, which is the id of the primary display assuming there is one.
*/
@@ -1292,6 +1296,93 @@
}
/**
+ * @return Whether the display supports reporting an hdr/sdr ratio. If this is false,
+ * {@link #getHdrSdrRatio()} will always be 1.0f
+ */
+ public boolean isHdrSdrRatioAvailable() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ return !Float.isNaN(mDisplayInfo.hdrSdrRatio);
+ }
+ }
+
+ /**
+ * @return The current hdr/sdr ratio expressed as the ratio of targetHdrPeakBrightnessInNits /
+ * targetSdrWhitePointInNits. If {@link #isHdrSdrRatioAvailable()} is false, this
+ * always returns 1.0f.
+ */
+ public float getHdrSdrRatio() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ return Float.isNaN(mDisplayInfo.hdrSdrRatio)
+ ? 1.0f : mDisplayInfo.hdrSdrRatio;
+ }
+ }
+
+ private int findHdrSdrRatioListenerLocked(Consumer<Display> listener) {
+ for (int i = 0; i < mHdrSdrRatioListeners.size(); i++) {
+ final HdrSdrRatioListenerWrapper wrapper = mHdrSdrRatioListeners.get(i);
+ if (wrapper.mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Registers a listener that will be invoked whenever the display's hdr/sdr ratio has changed.
+ * After receiving the callback on the specified Executor, call {@link #getHdrSdrRatio()} to
+ * get the updated value.
+ * If {@link #isHdrSdrRatioAvailable()} is false, then an IllegalStateException will be thrown
+ *
+ * @see #unregisterHdrSdrRatioChangedListener(Consumer)
+ * @param executor The executor to invoke the listener on
+ * @param listener The listener to invoke when the HDR/SDR ratio changes
+ * @throws IllegalStateException if {@link #isHdrSdrRatioAvailable()} is false
+ */
+ public void registerHdrSdrRatioChangedListener(@NonNull Executor executor,
+ @NonNull Consumer<Display> listener) {
+ if (!isHdrSdrRatioAvailable()) {
+ throw new IllegalStateException("HDR/SDR ratio changed not available");
+ }
+ HdrSdrRatioListenerWrapper toRegister = null;
+ synchronized (mLock) {
+ if (findHdrSdrRatioListenerLocked(listener) == -1) {
+ toRegister = new HdrSdrRatioListenerWrapper(listener);
+ mHdrSdrRatioListeners.add(toRegister);
+ } // else already listening, don't do anything
+ }
+ if (toRegister != null) {
+ // Although we only care about the HDR/SDR ratio changing, that can also come in the
+ // form of the larger DISPLAY_CHANGED event
+ mGlobal.registerDisplayListener(toRegister, executor,
+ DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
+ | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ }
+
+ }
+
+ /**
+ * @param listener The previously
+ * {@link #registerHdrSdrRatioChangedListener(Executor, Consumer) registered}
+ * hdr/sdr ratio listener to remove.
+ *
+ * @see #registerHdrSdrRatioChangedListener(Executor, Consumer)
+ */
+ public void unregisterHdrSdrRatioChangedListener(@NonNull Consumer<Display> listener) {
+ HdrSdrRatioListenerWrapper toRemove = null;
+ synchronized (mLock) {
+ int index = findHdrSdrRatioListenerLocked(listener);
+ if (index != -1) {
+ toRemove = mHdrSdrRatioListeners.remove(index);
+ }
+ }
+ if (toRemove != null) {
+ mGlobal.unregisterDisplayListener(toRemove);
+ }
+ }
+
+ /**
* Sets the default {@link Display.Mode} to use for the display. The display mode includes
* preference for resolution and refresh rate.
* If the mode specified is not supported by the display, then no mode change occurs.
@@ -2528,4 +2619,33 @@
}
}
}
+
+ private class HdrSdrRatioListenerWrapper implements DisplayManager.DisplayListener {
+ Consumer<Display> mListener;
+ float mLastReportedRatio = 1.f;
+
+ private HdrSdrRatioListenerWrapper(Consumer<Display> listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ // don't care
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ // don't care
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == getDisplayId()) {
+ float newRatio = getHdrSdrRatio();
+ if (newRatio != mLastReportedRatio) {
+ mListener.accept(Display.this);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 3a02c48..0368918 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -39,6 +39,8 @@
import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.display.BrightnessSynchronizer;
+
import java.util.Arrays;
import java.util.Objects;
@@ -340,6 +342,13 @@
@Nullable
public SurfaceControl.RefreshRateRange layoutLimitedRefreshRate;
+ /**
+ * The current hdr/sdr ratio for the display. If the display doesn't support hdr/sdr ratio
+ * queries then this is NaN
+ */
+ public float hdrSdrRatio = Float.NaN;
+
+
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -415,7 +424,8 @@
&& Objects.equals(roundedCorners, other.roundedCorners)
&& installOrientation == other.installOrientation
&& Objects.equals(displayShape, other.displayShape)
- && Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate);
+ && Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate)
+ && BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio);
}
@Override
@@ -471,6 +481,7 @@
installOrientation = other.installOrientation;
displayShape = other.displayShape;
layoutLimitedRefreshRate = other.layoutLimitedRefreshRate;
+ hdrSdrRatio = other.hdrSdrRatio;
}
public void readFromParcel(Parcel source) {
@@ -532,6 +543,7 @@
installOrientation = source.readInt();
displayShape = source.readTypedObject(DisplayShape.CREATOR);
layoutLimitedRefreshRate = source.readTypedObject(SurfaceControl.RefreshRateRange.CREATOR);
+ hdrSdrRatio = source.readFloat();
}
@Override
@@ -591,6 +603,7 @@
dest.writeInt(installOrientation);
dest.writeTypedObject(displayShape, flags);
dest.writeTypedObject(layoutLimitedRefreshRate, flags);
+ dest.writeFloat(hdrSdrRatio);
}
@Override
@@ -852,6 +865,12 @@
sb.append(Surface.rotationToString(installOrientation));
sb.append(", layoutLimitedRefreshRate ");
sb.append(layoutLimitedRefreshRate);
+ sb.append(", hdrSdrRatio ");
+ if (Float.isNaN(hdrSdrRatio)) {
+ sb.append("not_available");
+ } else {
+ sb.append(hdrSdrRatio);
+ }
sb.append("}");
return sb.toString();
}
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index bda707b..1898407 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -619,10 +619,11 @@
}
if (in.readInt() != 0) {
event.mDragSurface = SurfaceControl.CREATOR.createFromParcel(in);
+ event.mDragSurface.setUnreleasedWarningCallSite("DragEvent");
}
if (in.readInt() != 0) {
event.mDragAndDropPermissions =
- IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());;
+ IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
}
return event;
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 61582cd..1a5613e 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1351,7 +1351,7 @@
private int mDeviceId;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mSource;
- private int mDisplayId;
+ private int mDisplayId = INVALID_DISPLAY;
private @Nullable byte[] mHmac;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mMetaState;
@@ -1602,7 +1602,6 @@
mScanCode = scancode;
mFlags = flags;
mSource = source;
- mDisplayId = INVALID_DISPLAY;
}
/**
@@ -1628,7 +1627,6 @@
mDeviceId = deviceId;
mFlags = flags;
mSource = InputDevice.SOURCE_KEYBOARD;
- mDisplayId = INVALID_DISPLAY;
}
/**
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 8d8ddb9..5ea640c 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -296,6 +296,9 @@
taskId = in.readInt();
mode = in.readInt();
leash = in.readTypedObject(SurfaceControl.CREATOR);
+ if (leash != null) {
+ leash.setUnreleasedWarningCallSite("RemoteAnimationTarget[leash]");
+ }
isTranslucent = in.readBoolean();
clipRect = in.readTypedObject(Rect.CREATOR);
contentInsets = in.readTypedObject(Rect.CREATOR);
@@ -307,6 +310,9 @@
windowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
isNotInRecents = in.readBoolean();
startLeash = in.readTypedObject(SurfaceControl.CREATOR);
+ if (startLeash != null) {
+ startLeash.setUnreleasedWarningCallSite("RemoteAnimationTarget[startLeash]");
+ }
startBounds = in.readTypedObject(Rect.CREATOR);
taskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
allowEnterPip = in.readBoolean();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 8e09411..a99766f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -742,13 +742,6 @@
*/
public static final int POWER_MODE_ON_SUSPEND = 4;
- /**
- * internal representation of how to interpret pixel value, used only to convert to ColorSpace.
- */
- private static final int INTERNAL_DATASPACE_SRGB = 142671872;
- private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696;
- private static final int INTERNAL_DATASPACE_SCRGB = 411107328;
-
private void assignNativeObject(long nativeObject, String callsite) {
if (mNativeObject != 0) {
release();
@@ -1291,6 +1284,20 @@
}
/**
+ * Provides more information to show about the source of this SurfaceControl if it is finalized
+ * without being released. This is primarily intended for callers to update the call site after
+ * receiving a SurfaceControl from another process, which would otherwise get a generic default
+ * call site.
+ * @hide
+ */
+ public void setUnreleasedWarningCallSite(@NonNull String callsite) {
+ if (!isValid()) {
+ return;
+ }
+ mCloseGuard.openWithCallSite("release", callsite);
+ }
+
+ /**
* Checks whether two {@link SurfaceControl} objects represent the same surface.
*
* @param other The other object to check
@@ -1303,27 +1310,36 @@
}
/**
- * Returns the associated {@link Choreographer} instance with the
- * current instance of the SurfaceControl.
- * Must be called from a thread that already has a {@link android.os.Looper}
- * associated with it.
- * If there is no {@link Choreographer} associated with the SurfaceControl then a new instance
- * of the {@link Choreographer} is created.
+ * When called for the first time a new instance of the {@link Choreographer} is created
+ * with a {@link android.os.Looper} of the current thread. Every subsequent call will return
+ * the same instance of the Choreographer.
+ *
+ * @see #getChoreographer(Looper) to create Choreographer with a different
+ * looper than current thread looper.
*
* @hide
*/
@TestApi
public @NonNull Choreographer getChoreographer() {
- return getChoreographer(Looper.myLooper());
+ checkNotReleased();
+ synchronized (mChoreographerLock) {
+ if (mChoreographer == null) {
+ return getChoreographer(Looper.myLooper());
+ }
+ return mChoreographer;
+ }
}
/**
- * Returns the associated {@link Choreographer} instance with the
- * current instance of the SurfaceControl.
- * If there is no {@link Choreographer} associated with the SurfaceControl then a new instance
- * of the {@link Choreographer} is created.
+ * When called for the first time a new instance of the {@link Choreographer} is created with
+ * the sourced {@link android.os.Looper}. Every subsequent call will return the same
+ * instance of the Choreographer.
*
- * @param looper the choreographer is attached on this looper
+ * @see #getChoreographer()
+ *
+ * @throws IllegalStateException when a {@link Choreographer} instance exists with a different
+ * looper than sourced.
+ * @param looper the choreographer is attached on this looper.
*
* @hide
*/
@@ -1331,11 +1347,12 @@
public @NonNull Choreographer getChoreographer(@NonNull Looper looper) {
checkNotReleased();
synchronized (mChoreographerLock) {
- if (mChoreographer != null) {
- return mChoreographer;
+ if (mChoreographer == null) {
+ mChoreographer = Choreographer.getInstanceForSurfaceControl(mNativeHandle, looper);
+ } else if (!mChoreographer.isTheLooperSame(looper)) {
+ throw new IllegalStateException(
+ "Choreographer already exists with a different looper");
}
-
- mChoreographer = Choreographer.getInstanceForSurfaceControl(mNativeHandle, looper);
return mChoreographer;
}
}
@@ -2171,18 +2188,9 @@
ColorSpace[] colorSpaces = { srgb, srgb };
if (dataspaces.length == 2) {
for (int i = 0; i < 2; ++i) {
- switch(dataspaces[i]) {
- case INTERNAL_DATASPACE_DISPLAY_P3:
- colorSpaces[i] = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
- break;
- case INTERNAL_DATASPACE_SCRGB:
- colorSpaces[i] = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
- break;
- case INTERNAL_DATASPACE_SRGB:
- // Other dataspace is not recognized, use SRGB color space instead,
- // the default value of the array is already SRGB, thus do nothing.
- default:
- break;
+ ColorSpace cs = ColorSpace.getFromDataSpace(dataspaces[i]);
+ if (cs != null) {
+ colorSpaces[i] = cs;
}
}
}
@@ -3828,7 +3836,7 @@
* whose dataspace has RANGE_EXTENDED.
*
* @param sc The layer whose extended range brightness is being specified
- * @param currentBufferRatio The current sdr/hdr ratio of the current buffer. For example
+ * @param currentBufferRatio The current hdr/sdr ratio of the current buffer. For example
* if the buffer was rendered with a target SDR whitepoint of
* 100 nits and a max display brightness of 200 nits, this should
* be set to 2.0f.
@@ -3840,7 +3848,9 @@
* communicate extended content brightness information via
* metadata such as CTA861_3 or SMPTE2086.
*
- * @param desiredRatio The desired sdr/hdr ratio. This can be used to communicate the max
+ * Must be finite && >= 1.0f
+ *
+ * @param desiredRatio The desired hdr/sdr ratio. This can be used to communicate the max
* desired brightness range. This is similar to the "max luminance"
* value in other HDR metadata formats, but represented as a ratio of
* the target SDR whitepoint to the max display brightness. The system
@@ -3852,12 +3862,19 @@
* voluntarily reducing the requested range can help improve battery
* life as well as can improve quality by ensuring greater bit depth
* is allocated to the luminance range in use.
+ *
+ * Must be finite && >= 1.0f
* @return this
- * @hide
**/
public @NonNull Transaction setExtendedRangeBrightness(@NonNull SurfaceControl sc,
float currentBufferRatio, float desiredRatio) {
checkPreconditions(sc);
+ if (!Float.isFinite(currentBufferRatio) || currentBufferRatio < 1.0f) {
+ throw new IllegalArgumentException("currentBufferRatio must be finite && >= 1.0f");
+ }
+ if (!Float.isFinite(desiredRatio) || desiredRatio < 1.0f) {
+ throw new IllegalArgumentException("desiredRatio must be finite && >= 1.0f");
+ }
nativeSetExtendedRangeBrightness(mNativeObject, sc.mNativeObject, currentBufferRatio,
desiredRatio);
return this;
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index f7bdd09..ac50d09 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -32,6 +32,8 @@
import android.window.ISurfaceSyncGroup;
import android.window.WindowTokenClient;
+import dalvik.system.CloseGuard;
+
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -51,6 +53,7 @@
public class SurfaceControlViewHost {
private final static String TAG = "SurfaceControlViewHost";
private final ViewRootImpl mViewRoot;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
private WindowlessWindowManager mWm;
private SurfaceControl mSurfaceControl;
@@ -170,6 +173,7 @@
private SurfacePackage(Parcel in) {
mSurfaceControl = new SurfaceControl();
mSurfaceControl.readFromParcel(in);
+ mSurfaceControl.setUnreleasedWarningCallSite("SurfacePackage(Parcel)");
mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
in.readStrongBinder());
mInputToken = in.readStrongBinder();
@@ -291,9 +295,10 @@
/** @hide */
public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
- @NonNull WindowlessWindowManager wwm) {
+ @NonNull WindowlessWindowManager wwm, @NonNull String callsite) {
mWm = wwm;
mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
+ mCloseGuard.openWithCallSite("release", callsite);
addConfigCallback(c, d);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
@@ -315,15 +320,35 @@
*/
public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
@Nullable IBinder hostToken) {
+ this(context, display, hostToken, "untracked");
+ }
+
+ /**
+ * Construct a new SurfaceControlViewHost. The root Surface will be
+ * allocated internally and is accessible via getSurfacePackage().
+ *
+ * The {@param hostToken} parameter, primarily used for ANR reporting,
+ * must be obtained from whomever will be hosting the embedded hierarchy.
+ * It's accessible from {@link SurfaceView#getHostToken}.
+ *
+ * @param context The Context object for your activity or application.
+ * @param display The Display the hierarchy will be placed on.
+ * @param hostToken The host token, as discussed above.
+ * @param callsite The call site, used for tracking leakage of the host
+ * @hide
+ */
+ public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
+ @Nullable IBinder hostToken, @NonNull String callsite) {
mSurfaceControl = new SurfaceControl.Builder()
.setContainerLayer()
.setName("SurfaceControlViewHost")
- .setCallsite("SurfaceControlViewHost")
+ .setCallsite("SurfaceControlViewHost[" + callsite + "]")
.build();
mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
mSurfaceControl, hostToken);
mViewRoot = new ViewRootImpl(context, display, mWm, new WindowlessWindowLayout());
+ mCloseGuard.openWithCallSite("release", callsite);
addConfigCallback(context, display);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
@@ -349,7 +374,9 @@
if (mReleased) {
return;
}
- Log.e(TAG, "SurfaceControlViewHost finalized without being released: " + this);
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
// We aren't on the UI thread here so we need to pass false to doDie
mViewRoot.die(false /* immediate */);
WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
@@ -465,6 +492,7 @@
mViewRoot.die(true /* immediate */);
WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
mReleased = true;
+ mCloseGuard.close();
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d0f095c..480abe0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3741,7 +3741,7 @@
}
if (mAttachInfo.mContentCaptureEvents != null) {
- notifyContentCatpureEvents();
+ notifyContentCaptureEvents();
}
mIsInTraversal = false;
@@ -3782,7 +3782,7 @@
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
- private void notifyContentCatpureEvents() {
+ private void notifyContentCaptureEvents() {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
try {
MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 0f68cd0..82b390e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3569,6 +3569,22 @@
*/
public float preferredMaxDisplayRefreshRate;
+ /** Indicates whether this window wants the HDR conversion is disabled. */
+ public static final int DISPLAY_FLAG_DISABLE_HDR_CONVERSION = 1 << 0;
+
+ /**
+ * Flags that can be used to set display properties.
+ *
+ * @hide
+ */
+ @IntDef(flag = true, prefix = "DISPLAY_FLAG_", value = {
+ DISPLAY_FLAG_DISABLE_HDR_CONVERSION,
+ })
+ public @interface DisplayFlags {}
+
+ @DisplayFlags
+ private int mDisplayFlags;
+
/**
* An internal annotation for flags that can be specified to {@link #systemUiVisibility}
* and {@link #subtreeSystemUiVisibility}.
@@ -4286,6 +4302,24 @@
preservePreviousSurfaceInsets = preservePrevious;
}
+ /** Returns whether the HDR conversion is enabled for the window */
+ public boolean isHdrConversionEnabled() {
+ return ((mDisplayFlags & DISPLAY_FLAG_DISABLE_HDR_CONVERSION) == 0);
+ }
+
+ /**
+ * Enables/disables the HDR conversion for the window.
+ *
+ * By default, the HDR conversion is enabled for the window.
+ */
+ public void setHdrConversionEnabled(boolean enabled) {
+ if (!enabled) {
+ mDisplayFlags |= DISPLAY_FLAG_DISABLE_HDR_CONVERSION;
+ } else {
+ mDisplayFlags &= ~DISPLAY_FLAG_DISABLE_HDR_CONVERSION;
+ }
+ }
+
/**
* <p>Set the color mode of the window. Setting the color mode might
* override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
@@ -4460,6 +4494,7 @@
out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
checkNonRecursiveParams();
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
+ out.writeInt(mDisplayFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4530,6 +4565,7 @@
mWallpaperTouchEventsEnabled = in.readBoolean();
providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
+ mDisplayFlags = in.readInt();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -4565,6 +4601,8 @@
/** {@hide} */
public static final int PREFERRED_REFRESH_RATE_CHANGED = 1 << 21;
/** {@hide} */
+ public static final int DISPLAY_FLAGS_CHANGED = 1 << 22;
+ /** {@hide} */
public static final int PREFERRED_DISPLAY_MODE_ID = 1 << 23;
/** {@hide} */
public static final int ACCESSIBILITY_ANCHOR_CHANGED = 1 << 24;
@@ -4724,6 +4762,11 @@
changes |= PREFERRED_MAX_DISPLAY_REFRESH_RATE;
}
+ if (mDisplayFlags != o.mDisplayFlags) {
+ mDisplayFlags = o.mDisplayFlags;
+ changes |= DISPLAY_FLAGS_CHANGED;
+ }
+
if (systemUiVisibility != o.systemUiVisibility
|| subtreeSystemUiVisibility != o.subtreeSystemUiVisibility) {
systemUiVisibility = o.systemUiVisibility;
@@ -4979,6 +5022,10 @@
sb.append(" preferredMaxDisplayRefreshRate=");
sb.append(preferredMaxDisplayRefreshRate);
}
+ if (mDisplayFlags != 0) {
+ sb.append(" displayFlags=0x");
+ sb.append(Integer.toHexString(mDisplayFlags));
+ }
if (hasSystemUiListeners) {
sb.append(" sysuil=");
sb.append(hasSystemUiListeners);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index e87cd12..d1c4201 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -168,6 +168,9 @@
private static final String TAG = "Editor";
private static final boolean DEBUG_UNDO = false;
+ // TODO(nona): Make this configurable.
+ private static final boolean FLAG_USE_NEW_CONTEXT_MENU = false;
+
// Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
@@ -198,16 +201,6 @@
private static final int ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
private static final int ACTION_MODE_MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
- // Ordering constants used to place the Context Menu items in their menu.
- private static final int CONTEXT_MENU_ITEM_ORDER_UNDO = 2;
- private static final int CONTEXT_MENU_ITEM_ORDER_REDO = 3;
- private static final int CONTEXT_MENU_ITEM_ORDER_CUT = 4;
- private static final int CONTEXT_MENU_ITEM_ORDER_COPY = 5;
- private static final int CONTEXT_MENU_ITEM_ORDER_PASTE = 6;
- private static final int CONTEXT_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 7;
- private static final int CONTEXT_MENU_ITEM_ORDER_SELECT_ALL = 8;
- private static final int CONTEXT_MENU_ITEM_ORDER_SHARE = 9;
- private static final int CONTEXT_MENU_ITEM_ORDER_AUTOFILL = 10;
private static final int CONTEXT_MENU_ITEM_ORDER_REPLACE = 11;
private static final int CONTEXT_MENU_GROUP_UNDO_REDO = Menu.FIRST;
@@ -3142,56 +3135,76 @@
}
}
- menu.setOptionalIconsVisible(true);
+ final int menuItemOrderUndo = 2;
+ final int menuItemOrderRedo = 3;
+ final int menuItemOrderCut = 4;
+ final int menuItemOrderCopy = 5;
+ final int menuItemOrderPaste = 6;
+ final int menuItemOrderPasteAsPlainText;
+ final int menuItemOrderSelectAll;
+ final int menuItemOrderShare;
+ final int menuItemOrderAutofill;
+ if (FLAG_USE_NEW_CONTEXT_MENU) {
+ menuItemOrderPasteAsPlainText = 7;
+ menuItemOrderSelectAll = 8;
+ menuItemOrderShare = 9;
+ menuItemOrderAutofill = 10;
- final int keyboard = mTextView.getResources().getConfiguration().keyboard;
- menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
+ menu.setOptionalIconsVisible(true);
+ menu.setGroupDividerEnabled(true);
- menu.setGroupDividerEnabled(true);
+ final int keyboard = mTextView.getResources().getConfiguration().keyboard;
+ menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
+ } else {
+ menuItemOrderShare = 7;
+ menuItemOrderSelectAll = 8;
+ menuItemOrderAutofill = 10;
+ menuItemOrderPasteAsPlainText = 11;
+ }
- menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, CONTEXT_MENU_ITEM_ORDER_UNDO,
+ menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
com.android.internal.R.string.undo)
.setAlphabeticShortcut('z')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canUndo());
- menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, CONTEXT_MENU_ITEM_ORDER_REDO,
+ menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo,
com.android.internal.R.string.redo)
.setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canRedo());
- menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, CONTEXT_MENU_ITEM_ORDER_CUT,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut,
com.android.internal.R.string.cut)
.setAlphabeticShortcut('x')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canCut());
- menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, CONTEXT_MENU_ITEM_ORDER_COPY,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy,
com.android.internal.R.string.copy)
.setAlphabeticShortcut('c')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canCopy());
- menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, CONTEXT_MENU_ITEM_ORDER_PASTE,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste,
com.android.internal.R.string.paste)
.setAlphabeticShortcut('v')
.setEnabled(mTextView.canPaste())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
- CONTEXT_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
+ menuItemOrderPasteAsPlainText,
com.android.internal.R.string.paste_as_plain_text)
.setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setEnabled(mTextView.canPasteAsPlainText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
- CONTEXT_MENU_ITEM_ORDER_SELECT_ALL, com.android.internal.R.string.selectAll)
+ menuItemOrderSelectAll, com.android.internal.R.string.selectAll)
.setAlphabeticShortcut('a')
.setEnabled(mTextView.canSelectAllText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, CONTEXT_MENU_ITEM_ORDER_SHARE,
+ menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare,
com.android.internal.R.string.share)
.setEnabled(mTextView.canShare())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, CONTEXT_MENU_ITEM_ORDER_AUTOFILL,
+ menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
android.R.string.autofill)
.setEnabled(mTextView.canRequestAutofill())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index bc9f74e..bdaad2b 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -333,7 +333,8 @@
SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext,
viewContext.getDisplay(),
- surfaceView.getHostToken());
+ surfaceView.getHostToken(),
+ "SplashScreenView");
ImageView imageView = new ImageView(viewContext);
imageView.setBackground(mIconDrawable);
viewHost.setView(imageView, mIconSize, mIconSize);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c8a69e2..0b43eb5 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -198,6 +198,7 @@
in.readTypedList(mChanges, Change.CREATOR);
mRootLeash = new SurfaceControl();
mRootLeash.readFromParcel(in);
+ mRootLeash.setUnreleasedWarningCallSite("TransitionInfo");
mRootOffset.readFromParcel(in);
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
}
@@ -845,6 +846,9 @@
private HardwareBuffer mThumbnail;
private int mAnimations;
private @ColorInt int mBackgroundColor;
+ // Customize activity transition animation
+ private CustomActivityTransition mCustomActivityOpenTransition;
+ private CustomActivityTransition mCustomActivityCloseTransition;
private AnimationOptions(int type) {
mType = type;
@@ -860,6 +864,15 @@
mTransitionBounds.readFromParcel(in);
mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
mAnimations = in.readInt();
+ mCustomActivityOpenTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
+ mCustomActivityCloseTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
+ }
+
+ /** Make basic customized animation for a package */
+ public static AnimationOptions makeCommonAnimOptions(String packageName) {
+ AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
+ options.mPackageName = packageName;
+ return options;
}
public static AnimationOptions makeAnimOptionsFromLayoutParameters(
@@ -870,6 +883,27 @@
return options;
}
+ /** Add customized window animations */
+ public void addOptionsFromLayoutParameters(WindowManager.LayoutParams lp) {
+ mAnimations = lp.windowAnimations;
+ }
+
+ /** Add customized activity animation attributes */
+ public void addCustomActivityTransition(boolean isOpen,
+ int enterResId, int exitResId, int backgroundColor) {
+ CustomActivityTransition customTransition = isOpen
+ ? mCustomActivityOpenTransition : mCustomActivityCloseTransition;
+ if (customTransition == null) {
+ customTransition = new CustomActivityTransition();
+ if (isOpen) {
+ mCustomActivityOpenTransition = customTransition;
+ } else {
+ mCustomActivityCloseTransition = customTransition;
+ }
+ }
+ customTransition.addCustomActivityTransition(enterResId, exitResId, backgroundColor);
+ }
+
public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
@@ -945,6 +979,11 @@
return mAnimations;
}
+ /** Return customized activity transition if existed. */
+ public CustomActivityTransition getCustomActivityTransition(boolean open) {
+ return open ? mCustomActivityOpenTransition : mCustomActivityCloseTransition;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
@@ -956,6 +995,8 @@
mTransitionBounds.writeToParcel(dest, flags);
dest.writeTypedObject(mThumbnail, flags);
dest.writeInt(mAnimations);
+ dest.writeTypedObject(mCustomActivityOpenTransition, flags);
+ dest.writeTypedObject(mCustomActivityCloseTransition, flags);
}
@NonNull
@@ -996,5 +1037,68 @@
return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
+ " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
}
+
+ /** Customized activity transition. */
+ public static class CustomActivityTransition implements Parcelable {
+ private int mCustomEnterResId;
+ private int mCustomExitResId;
+ private int mCustomBackgroundColor;
+
+ /** Returns customize activity animation enter resource id */
+ public int getCustomEnterResId() {
+ return mCustomEnterResId;
+ }
+
+ /** Returns customize activity animation exit resource id */
+ public int getCustomExitResId() {
+ return mCustomExitResId;
+ }
+
+ /** Returns customize activity animation background color */
+ public int getCustomBackgroundColor() {
+ return mCustomBackgroundColor;
+ }
+ CustomActivityTransition() {}
+
+ CustomActivityTransition(Parcel in) {
+ mCustomEnterResId = in.readInt();
+ mCustomExitResId = in.readInt();
+ mCustomBackgroundColor = in.readInt();
+ }
+
+ /** Add customized activity animation attributes */
+ public void addCustomActivityTransition(
+ int enterResId, int exitResId, int backgroundColor) {
+ mCustomEnterResId = enterResId;
+ mCustomExitResId = exitResId;
+ mCustomBackgroundColor = backgroundColor;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCustomEnterResId);
+ dest.writeInt(mCustomExitResId);
+ dest.writeInt(mCustomBackgroundColor);
+ }
+
+ @NonNull
+ public static final Creator<CustomActivityTransition> CREATOR =
+ new Creator<CustomActivityTransition>() {
+ @Override
+ public CustomActivityTransition createFromParcel(Parcel in) {
+ return new CustomActivityTransition(in);
+ }
+
+ @Override
+ public CustomActivityTransition[] newArray(int size) {
+ return new CustomActivityTransition[size];
+ }
+ };
+ }
}
}
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index b0a4b54..8b41829 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -167,8 +167,9 @@
*/
@UnsupportedAppUsage
public String getFullNameInUiLanguage() {
+ Locale locale = mLocale.stripExtensions();
// We don't cache the UI name because the default locale keeps changing
- return LocaleHelper.getDisplayName(mLocale, true /* sentence case */);
+ return LocaleHelper.getDisplayName(locale, true /* sentence case */);
}
private String getLangScriptKey() {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 1084c71..f42bc81 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -48,25 +48,10 @@
public static final String NAS_MAX_SUGGESTIONS = "nas_max_suggestions";
/**
- * Whether the Notification Assistant can change ranking.
- */
- public static final String ENABLE_NAS_RANKING = "enable_nas_ranking";
-
- /**
- * Whether the Notification Assistant can prioritize notification.
- */
- public static final String ENABLE_NAS_PRIORITIZER = "enable_nas_prioritizer";
-
- /**
* Whether to enable feedback UI for Notification Assistant
*/
public static final String ENABLE_NAS_FEEDBACK = "enable_nas_feedback";
- /**
- * Whether the Notification Assistant can label a notification not a conversation
- */
- public static final String ENABLE_NAS_NOT_CONVERSATION = "enable_nas_not_conversation";
-
// Flags related to screenshot intelligence
/**
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index 205c5fd..d2b612a 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -73,6 +73,23 @@
mOnPropertiesChangedListener);
}
+ public void registerForCurrentUser() {
+ ContentResolver r = mContext.getContentResolver();
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT),
+ false, this);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
+ false, this);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+ false, this);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ runnable -> mMainHandler.post(runnable),
+ mOnPropertiesChangedListener);
+ }
+
public void unregister() {
mContext.getContentResolver().unregisterContentObserver(this);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b79c540..fc26766 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -332,11 +332,8 @@
"libdl",
"libdl_android",
"libtimeinstate",
- "libtflite",
"server_configurable_flags",
"libimage_io",
- "libjpegdecoder",
- "libjpegencoder",
"libjpegrecoverymap",
],
export_shared_lib_headers: [
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index bcb0da3..d2d87d6 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -427,7 +427,7 @@
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return 0;
} else if (err != NO_ERROR) {
- jniThrowException(env, OutOfResourcesException, NULL);
+ jniThrowException(env, OutOfResourcesException, statusToString(err).c_str());
return 0;
}
diff --git a/core/proto/android/app/OWNERS b/core/proto/android/app/OWNERS
index 4d76aee..a137ea9 100644
--- a/core/proto/android/app/OWNERS
+++ b/core/proto/android/app/OWNERS
@@ -1,2 +1,3 @@
+per-file appstartinfo.proto = file:/services/core/java/com/android/server/am/OWNERS
per-file location_time_zone_manager.proto = file:platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
per-file time_zone_detector.proto = file:platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/proto/android/server/OWNERS b/core/proto/android/server/OWNERS
index 72d39bf..c6d3cda 100644
--- a/core/proto/android/server/OWNERS
+++ b/core/proto/android/server/OWNERS
@@ -1 +1,2 @@
+per-file activitymanagerservice.proto = file:/services/core/java/com/android/server/am/OWNERS
per-file window*.proto = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bb3fc01..a4818c7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2824,6 +2824,13 @@
<permission android:name="android.permission.BIND_INCALL_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by a {@link android.telecom.CallStreamingService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @SystemApi @hide-->
+ <permission android:name="android.permission.BIND_CALL_STREAMING_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Allows to query ongoing call details and manage ongoing calls
<p>Protection level: signature|appop -->
<permission android:name="android.permission.MANAGE_ONGOING_CALLS"
@@ -5538,6 +5545,12 @@
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
android:protectionLevel="signature|installer" />
+ <!-- @SystemApi Allows an application to manage the holders of roles associated with default
+ applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"
+ android:protectionLevel="signature|role" />
+
<!-- @SystemApi Allows an application to bypass role qualification. This allows switching role
holders to otherwise non eligible holders. Only the shell is allowed to do this, the
qualification for the shell role itself cannot be bypassed, and each role needs to
@@ -6078,6 +6091,11 @@
<permission android:name="android.permission.REGISTER_STATS_PULL_ATOM"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to read restricted stats from statsd.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_RESTRICTED_STATS"
+ android:protectionLevel="internal|privileged" />
+
<!-- @SystemApi Allows an application to control the backup and restore process.
<p>Not for use by third-party applications.
@hide pending API council -->
@@ -8097,7 +8115,7 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service android:name="com.android.server.healthconnect.storage.AutoDeleteService"
+ <service android:name="com.android.server.healthconnect.HealthConnectDailyService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
diff --git a/core/res/res/drawable/ic_phone_disabled.xml b/core/res/res/drawable/ic_phone_disabled.xml
new file mode 100644
index 0000000..6e60912
--- /dev/null
+++ b/core/res/res/drawable/ic_phone_disabled.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M40.65,45.15 L28.9,33.45Q23.55,37.8 18.5,39.925Q13.45,42.05 8.6,42.05Q7.45,42.05 6.7,41.4Q5.95,40.75 5.95,39.75V33Q5.95,32.2 6.45,31.6Q6.95,31 7.7,30.85L13.65,29.55Q14.3,29.4 14.95,29.625Q15.6,29.85 16.1,30.4L20.85,35.3Q22.3,34.6 23.9,33.45Q25.5,32.3 26.75,31.3L3.2,7.7L5.35,5.55L42.8,43.05ZM18.1,36.75 L14.15,32.6Q14.15,32.6 14.15,32.6Q14.15,32.6 14.15,32.6L9,33.65Q9,33.65 9,33.65Q9,33.65 9,33.65V39Q9,39 9,39Q9,39 9,39Q11.25,38.9 13.65,38.3Q16.05,37.7 18.1,36.75ZM33.15,29.15 L31,27Q32.05,25.75 33.125,24.175Q34.2,22.6 35.05,21.3L30.05,16.25Q29.65,15.85 29.5,15.275Q29.35,14.7 29.5,14L30.8,7.75Q30.95,6.95 31.475,6.475Q32,6 32.7,6H39.7Q40.65,6 41.325,6.65Q42,7.3 42,8.25Q42,13.35 39.6,18.975Q37.2,24.6 33.15,29.15ZM36.45,18.45Q37.75,15.45 38.35,13.2Q38.95,10.95 38.9,9Q38.9,9 38.9,9Q38.9,9 38.9,9H33.6Q33.6,9 33.6,9Q33.6,9 33.6,9L32.45,14.45Q32.45,14.45 32.45,14.45Q32.45,14.45 32.45,14.45ZM36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45Q36.45,18.45 36.45,18.45ZM18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Q18.1,36.75 18.1,36.75Z"/>
+</vector>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 11a4ed7..d2ee5de 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1648,14 +1648,25 @@
or has been granted the access to one of the attached USB devices/accessories.
-->
<flag name="connectedDevice" value="0x10" />
- <!-- Managing a media projection session, e.g, for screen recording or taking
- screenshots.
+ <!-- Managing a {@link android.media.projection.MediaProjection MediaProjection} session,
+ e.g., for screen recording or takingscreenshots.
- <p>For apps with <code>targetSdkVersion</code>
- {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
- service with this type will require permission
- {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user
- must have allowed the screen capture request from this app.
+ <p>
+ To capture through {@link android.media.projection.MediaProjection}, an app must start
+ a foreground service with the type corresponding to this constant. This type should
+ only be used for {@link android.media.projection.MediaProjection}. Capturing screen
+ contents via
+ {@link android.media.projection.MediaProjection#createVirtualDisplay(String, int, int,
+ int, int, android.view.Surface, android.hardware.display.VirtualDisplay.Callback,
+ android.os.Handler) createVirtualDisplay} conveniently allows recording, presenting
+ screen contents into a meeting, taking screenshots, or several other scenarios.
+ </p>
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a
+ foreground service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user
+ must have allowed the screen capture request from this app.
-->
<flag name="mediaProjection" value="0x20" />
<!-- Use the camera device or record video.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c75ee93..8e5ae9c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2997,6 +2997,12 @@
<string name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string>
+ <!-- Package name of the system service that implements the shared connectivity service -->
+ <string translatable="false" name="shared_connectivity_service_package"></string>
+
+ <!-- Intent action used when binding to the shared connectivity service -->
+ <string translatable="false" name="shared_connectivity_service_intent_action"></string>
+
<!-- Component name of the activity that shows the usb containment status. -->
<string name="config_usbContaminantActivity" translatable="false"
>com.android.systemui/com.android.systemui.usb.UsbContaminantActivity</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 26927f8..47d771f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -556,6 +556,16 @@
<!-- Title for the button that turns work profile on. To be used in a notification
[CHAR LIMIT=NONE] -->
<string name="personal_apps_suspended_turn_profile_on">Turn on</string>
+ <!-- Notification title. This notification lets the user know that they will be unable to
+ receive phone calls or texts until the work profile is turned on. [CHAR LIMIT=40] -->
+ <string name="work_profile_telephony_paused_title">Calls and messages are off</string>
+ <!-- Notification text. This notification lets the user know that they will be unable to
+ receive phone calls or texts until the work profile is turned on. [CHAR LIMIT=NONE] -->
+ <string name="work_profile_telephony_paused_text">You have paused work apps.
+ You won\'t receive phone calls or text messages.</string>
+ <!-- Label for notification button. This button lets the user turn the work profile on.
+ [CHAR LIMIT=15] -->
+ <string name="work_profile_telephony_paused_turn_on_button">Unpause work apps</string>
<!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. -->
<string name="me">Me</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d7cdf73..dcd7a31 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1190,6 +1190,9 @@
<java-symbol type="string" name="personal_apps_suspension_soon_text" />
<java-symbol type="string" name="personal_apps_suspension_text" />
<java-symbol type="string" name="personal_apps_suspended_turn_profile_on" />
+ <java-symbol type="string" name="work_profile_telephony_paused_title" />
+ <java-symbol type="string" name="work_profile_telephony_paused_text" />
+ <java-symbol type="string" name="work_profile_telephony_paused_turn_on_button" />
<java-symbol type="string" name="notification_work_profile_content_description" />
<java-symbol type="string" name="factory_reset_warning" />
<java-symbol type="string" name="factory_reset_message" />
@@ -1410,6 +1413,7 @@
<java-symbol type="drawable" name="btn_borderless_rect" />
<java-symbol type="drawable" name="ic_phone" />
+ <java-symbol type="drawable" name="ic_phone_disabled" />
<java-symbol type="drawable" name="ic_bt_headphones_a2dp" />
<java-symbol type="drawable" name="ic_bt_headset_hfp" />
<java-symbol type="drawable" name="ic_bt_hearing_aid" />
@@ -4938,6 +4942,9 @@
<java-symbol type="bool" name="config_stopSystemPackagesByDefault"/>
<java-symbol type="string" name="config_wearServiceComponent" />
+ <java-symbol type="string" name="shared_connectivity_service_package" />
+ <java-symbol type="string" name="shared_connectivity_service_intent_action" />
+
<!-- Whether to show weather on the lockscreen by default. -->
<java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
</resources>
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
index 2a1881e..f97099d 100644
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -20,28 +20,13 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.BackgroundThread;
-
-import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.time.Duration;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -50,23 +35,11 @@
private static final String SENSOR_NAME = "VirtualSensorName";
private static final String SENSOR_VENDOR = "VirtualSensorVendor";
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private VirtualSensor.SensorStateChangeCallback mSensorCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- }
-
@Test
public void parcelAndUnparcel_matches() {
final VirtualSensorConfig originalConfig =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
- .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
.build();
final Parcel parcel = Parcel.obtain();
originalConfig.writeToParcel(parcel, /* flags= */ 0);
@@ -76,7 +49,6 @@
assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
- assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
}
@Test
@@ -84,23 +56,5 @@
final VirtualSensorConfig config =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
assertThat(config.getVendor()).isNull();
- assertThat(config.getStateChangeCallback()).isNull();
- }
-
- @Test
- public void sensorConfig_sensorCallbackInvocation() throws Exception {
- final VirtualSensorConfig config =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
- .build();
-
- final Duration samplingPeriod = Duration.ofMillis(123);
- final Duration batchLatency = Duration.ofMillis(456);
-
- config.getStateChangeCallback().onStateChanged(true,
- (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
- (int) MILLISECONDS.toMicros(batchLatency.toMillis()));
-
- verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
}
}
diff --git a/core/tests/coretests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/coretests/src/android/hardware/hdmi/HdmiUtilsTest.java
index d8799cb..34ca502 100644
--- a/core/tests/coretests/src/android/hardware/hdmi/HdmiUtilsTest.java
+++ b/core/tests/coretests/src/android/hardware/hdmi/HdmiUtilsTest.java
@@ -17,12 +17,15 @@
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+@Presubmit
@SmallTest
@RunWith(JUnit4.class)
/** Tests for {@link HdmiUtils} class. */
diff --git a/core/tests/coretests/src/android/view/KeyEventTest.java b/core/tests/coretests/src/android/view/KeyEventTest.java
index bbb25a3..cbf11c4 100644
--- a/core/tests/coretests/src/android/view/KeyEventTest.java
+++ b/core/tests/coretests/src/android/view/KeyEventTest.java
@@ -18,8 +18,8 @@
import static android.view.Display.INVALID_DISPLAY;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -27,6 +27,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,7 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
@Presubmit
-public class KeyEventTest {
+public final class KeyEventTest {
private static final int DOWN_TIME = 50;
private static final long EVENT_TIME = 100;
@@ -52,22 +55,14 @@
private static final int ID_SOURCE_MASK = 0x3 << 30;
+ @Rule public final Expect expect = Expect.create();
+
@Test
public void testObtain() {
KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS);
- assertEquals(DOWN_TIME, keyEvent.getDownTime());
- assertEquals(EVENT_TIME, keyEvent.getEventTime());
- assertEquals(ACTION, keyEvent.getAction());
- assertEquals(KEYCODE, keyEvent.getKeyCode());
- assertEquals(REPEAT, keyEvent.getRepeatCount());
- assertEquals(METASTATE, keyEvent.getMetaState());
- assertEquals(DEVICE_ID, keyEvent.getDeviceId());
- assertEquals(SCAN_CODE, keyEvent.getScanCode());
- assertEquals(FLAGS, keyEvent.getFlags());
- assertEquals(SOURCE, keyEvent.getSource());
- assertEquals(INVALID_DISPLAY, keyEvent.getDisplayId());
- assertEquals(CHARACTERS, keyEvent.getCharacters());
+
+ assertHasDefaultFields(keyEvent, INVALID_DISPLAY);
}
@Test
@@ -75,6 +70,7 @@
KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS);
KeyEvent keyEvent2 = KeyEvent.obtain(keyEvent);
+
compareKeys(keyEvent, keyEvent2);
}
@@ -83,18 +79,8 @@
final int displayId = 5;
KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, displayId, CHARACTERS);
- assertEquals(DOWN_TIME, keyEvent.getDownTime());
- assertEquals(EVENT_TIME, keyEvent.getEventTime());
- assertEquals(ACTION, keyEvent.getAction());
- assertEquals(KEYCODE, keyEvent.getKeyCode());
- assertEquals(REPEAT, keyEvent.getRepeatCount());
- assertEquals(METASTATE, keyEvent.getMetaState());
- assertEquals(DEVICE_ID, keyEvent.getDeviceId());
- assertEquals(SCAN_CODE, keyEvent.getScanCode());
- assertEquals(FLAGS, keyEvent.getFlags());
- assertEquals(SOURCE, keyEvent.getSource());
- assertEquals(displayId, keyEvent.getDisplayId());
- assertEquals(CHARACTERS, keyEvent.getCharacters());
+
+ assertHasDefaultFields(keyEvent, displayId);
}
/**
@@ -109,8 +95,8 @@
for (int i = 0; i < 500; ++i) {
KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS);
- assertFalse("Found duplicate ID in round " + i,
- set.contains(keyEvent.getId()));
+ assertWithMessage("event IDs (event generated on round %s: %s)", i, keyEvent)
+ .that(set).doesNotContain(keyEvent.getId());
set.add(keyEvent.getSequenceNumber());
}
}
@@ -120,8 +106,8 @@
Set<Integer> set = new HashSet<>();
for (int i = 0; i < 500; ++i) {
KeyEvent keyEvent = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT);
- assertFalse("Found duplicate sequence number in round " + i,
- set.contains(keyEvent.getId()));
+ assertWithMessage("sequence numbers (event generated on round %s: %s)", i, keyEvent)
+ .that(set).doesNotContain(keyEvent.getSequenceNumber());
set.add(keyEvent.getSequenceNumber());
}
}
@@ -131,7 +117,7 @@
for (int i = 0; i < 500; ++i) {
KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS);
- assertEquals(0x3 << 30, ID_SOURCE_MASK & keyEvent.getId());
+ assertThat((ID_SOURCE_MASK & keyEvent.getId())).isEqualTo((0x3 << 30));
}
}
@@ -139,48 +125,165 @@
public void testConstructorGeneratesIdWithRightSource() {
for (int i = 0; i < 500; ++i) {
KeyEvent keyEvent = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT);
- assertEquals(0x3 << 30, ID_SOURCE_MASK & keyEvent.getId());
+ assertThat((ID_SOURCE_MASK & keyEvent.getId())).isEqualTo((0x3 << 30));
}
}
@Test
public void testParcelUnparcel() {
KeyEvent key1 = createKey();
- Parcel parcel = Parcel.obtain();
- key1.writeToParcel(parcel, 0 /*flags*/);
- parcel.setDataPosition(0);
+ KeyEvent key2;
- KeyEvent key2 = KeyEvent.CREATOR.createFromParcel(parcel);
- parcel.recycle();
+ Parcel parcel = Parcel.obtain();
+ try {
+ key1.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ key2 = KeyEvent.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
compareKeys(key1, key2);
}
@Test
- public void testConstructor() {
+ public void testCopyConstructor() {
KeyEvent key1 = createKey();
KeyEvent key2 = new KeyEvent(key1);
+
compareKeys(key1, key2);
}
+ @Test
+ public void testConstructorWith10Args() {
+ KeyEvent key = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE, FLAGS, SOURCE);
+
+ assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, CHARACTERS);
+ }
+
+ @Test
+ public void testConstructorWith9Args() {
+ KeyEvent key = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE, FLAGS);
+
+ assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE, FLAGS, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ }
+
+ @Test
+ public void testConstructorWith8Args() {
+ KeyEvent key = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE);
+
+ assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY,
+ CHARACTERS);
+ }
+
+ @Test
+ public void testConstructorWith6Args() {
+ KeyEvent key = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE);
+
+ assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, /* flags= */ 0,
+ /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ }
+
+ @Test
+ public void testConstructorWith5Args() {
+ KeyEvent key = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT);
+
+ assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
+ /* metaState= */ 0, KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0,
+ /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ }
+
+ @Test
+ public void testConstructorWith4Args() {
+ KeyEvent key = new KeyEvent(42, CHARACTERS, DEVICE_ID, FLAGS);
+
+ assertKeyEventFields(key, /* downTime= */ 42, /* eventTime= */ 42, KeyEvent.ACTION_MULTIPLE,
+ KeyEvent.KEYCODE_UNKNOWN, /* repeat= */ 0, /* metaState= */ 0, DEVICE_ID,
+ /* scanCode= */ 0, FLAGS, InputDevice.SOURCE_KEYBOARD, INVALID_DISPLAY, CHARACTERS);
+ }
+
+ @Test
+ public void testConstructorWith2rgs() {
+ KeyEvent key = new KeyEvent(ACTION, KEYCODE);
+
+ assertKeyEventFields(key, /* downTime= */ 0, /* eventTime= */ 0, ACTION, KEYCODE,
+ /* repeat= */ 0, /* metaState= */ 0, KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scanCode= */ 0, FLAGS, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ }
+
private static KeyEvent createKey() {
return KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, CHARACTERS);
}
- private static void compareKeys(KeyEvent key1, KeyEvent key2) {
- assertEquals(key1.getId(), key2.getId());
- assertEquals(key1.getDownTime(), key2.getDownTime());
- assertEquals(key1.getEventTime(), key2.getEventTime());
- assertEquals(key1.getAction(), key2.getAction());
- assertEquals(key1.getKeyCode(), key2.getKeyCode());
- assertEquals(key1.getRepeatCount(), key2.getRepeatCount());
- assertEquals(key1.getMetaState(), key2.getMetaState());
- assertEquals(key1.getDeviceId(), key2.getDeviceId());
- assertEquals(key1.getScanCode(), key2.getScanCode());
- assertEquals(key1.getFlags(), key2.getFlags());
- assertEquals(key1.getSource(), key2.getSource());
- assertEquals(key1.getDisplayId(), key2.getDisplayId());
- assertEquals(key1.getCharacters(), key2.getCharacters());
+ private void compareKeys(KeyEvent key1, KeyEvent key2) {
+ expect.withMessage("id (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getId()).isEqualTo(key1.getId());
+ expect.withMessage("downTime (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getDownTime()).isEqualTo(key1.getDownTime());
+ expect.withMessage("eventTime (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getEventTime()).isEqualTo(key1.getEventTime());
+ expect.withMessage("action (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getAction()).isEqualTo(key1.getAction());
+ expect.withMessage("keyCode (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getKeyCode()).isEqualTo(key1.getKeyCode());
+ expect.withMessage("repatCount (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getRepeatCount()).isEqualTo(key1.getRepeatCount());
+ expect.withMessage("metaState (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getMetaState()).isEqualTo(key1.getMetaState());
+ expect.withMessage("deviceId (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getDeviceId()).isEqualTo(key1.getDeviceId());
+ expect.withMessage("scanCode (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getScanCode()).isEqualTo(key1.getScanCode());
+ expect.withMessage("flags (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getFlags()).isEqualTo(key1.getFlags());
+ expect.withMessage("source (key1=%s, key2=%s)", key1, key2)
+ .that(key2.getSource()).isEqualTo(key1.getSource());
+ expect.withMessage("displayId(key1=%s, key2=%s)", key1, key2)
+ .that(key2.getDisplayId()).isEqualTo(key1.getDisplayId());
+ expect.withMessage("characters(key1=%s, key2=%s)", key1, key2)
+ .that(key2.getCharacters()).isEqualTo(key1.getCharacters());
+ }
+
+ private void assertHasDefaultFields(KeyEvent keyEvent, int displayId) {
+ assertKeyEventFields(keyEvent, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+ DEVICE_ID, SCAN_CODE, FLAGS, InputDevice.SOURCE_KEYBOARD, displayId, CHARACTERS);
+ }
+
+ private void assertKeyEventFields(KeyEvent keyEvent, long downTime, long eventTime,
+ int action, int keyCode, int repeat, int metaState, int deviceId, int scanCode,
+ int flags, int source, int displayId, String characters) {
+ expect.withMessage("downTime on %s", keyEvent)
+ .that(keyEvent.getDownTime()).isEqualTo(downTime);
+ expect.withMessage("eventTime on %s", keyEvent)
+ .that(keyEvent.getEventTime()).isEqualTo(eventTime);
+ expect.withMessage("action on %s", keyEvent)
+ .that(keyEvent.getAction()).isEqualTo(action);
+ expect.withMessage("keyCode on %s", keyEvent)
+ .that(keyEvent.getKeyCode()).isEqualTo(keyCode);
+ expect.withMessage("repeatCount on %s", keyEvent)
+ .that(keyEvent.getRepeatCount()).isEqualTo(repeat);
+ expect.withMessage("metaState on %s", keyEvent)
+ .that(keyEvent.getMetaState()).isEqualTo(metaState);
+ expect.withMessage("deviceId on %s", keyEvent)
+ .that(keyEvent.getDeviceId()).isEqualTo(deviceId);
+ expect.withMessage("scanCode on %s", keyEvent)
+ .that(keyEvent.getScanCode()).isEqualTo(scanCode);
+ expect.withMessage("flags on %s", keyEvent)
+ .that(keyEvent.getFlags()).isEqualTo(flags);
+ expect.withMessage("source on %s", keyEvent)
+ .that(keyEvent.getSource()).isEqualTo(source);
+ expect.withMessage("displayId on %s", keyEvent)
+ .that(keyEvent.getDisplayId()).isEqualTo(displayId);
+ expect.withMessage("characters on %s", keyEvent)
+ .that(keyEvent.getCharacters()).isEqualTo(characters);
}
}
diff --git a/core/tests/hdmitests/Android.bp b/core/tests/hdmitests/Android.bp
index f49e053..3d04937 100644
--- a/core/tests/hdmitests/Android.bp
+++ b/core/tests/hdmitests/Android.bp
@@ -29,9 +29,11 @@
"androidx.test.rules",
"frameworks-base-testutils",
"guava-android-testlib",
+ "platform-test-annotations",
"truth-prebuilt",
],
libs: ["android.test.runner"],
platform_apis: true,
certificate: "platform",
+ test_suites: ["device-tests"],
}
diff --git a/core/tests/hdmitests/AndroidTest.xml b/core/tests/hdmitests/AndroidTest.xml
index 0c8da28..7376004 100644
--- a/core/tests/hdmitests/AndroidTest.xml
+++ b/core/tests/hdmitests/AndroidTest.xml
@@ -22,6 +22,9 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="HdmiCecTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true" />
+ </target_preparer>
<option name="test-suite-tag" value="apct"/>
<option name="test-tag" value="HdmiTests"/>
@@ -30,5 +33,6 @@
<option name="package" value="android.hardware.hdmi" />
<option name="hidden-api-checks" value="false"/>
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ <option name="test-filter-dir" value="/data/data/android.hardware.hdmi" />
</test>
</configuration>
\ No newline at end of file
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java
index 875ab38..9005afb 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java
@@ -22,6 +22,8 @@
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import com.google.common.testing.EqualsTester;
@@ -31,6 +33,7 @@
import org.junit.runners.JUnit4;
/** Tests for {@link DeviceFeatures} */
+@Presubmit
@RunWith(JUnit4.class)
@SmallTest
public class DeviceFeaturesTest {
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index e16a2f8..bb3e768 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -18,6 +18,7 @@
import android.os.Handler;
import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.filters.SmallTest;
@@ -34,6 +35,7 @@
/**
* Tests for {@link HdmiAudioSystemClient}
*/
+@Presubmit
@RunWith(JUnit4.class)
@SmallTest
public class HdmiAudioSystemClientTest {
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
index 5f7468e..5039fe8 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
@@ -16,6 +16,8 @@
package android.hardware.hdmi;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import com.google.common.testing.EqualsTester;
@@ -25,6 +27,7 @@
import org.junit.runners.JUnit4;
/** Tests for {@link HdmiDeviceInfo} */
+@Presubmit
@RunWith(JUnit4.class)
@SmallTest
public class HdmiDeviceInfoTest {
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
index 2bce747..fde1122 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
@@ -16,6 +16,8 @@
package android.hardware.hdmi;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import com.google.common.testing.EqualsTester;
@@ -25,6 +27,7 @@
import org.junit.runners.JUnit4;
/** Tests for {@link HdmiPortInfo} */
+@Presubmit
@RunWith(JUnit4.class)
@SmallTest
public class HdmiPortInfoTest {
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
index fdc6b84..13212ce 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -26,6 +28,7 @@
/**
* Tests for {@link HdmiUtils}.
*/
+@Presubmit
@RunWith(JUnit4.class)
@SmallTest
public class HdmiUtilsTest {
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 046373d..b1abc2a1 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1899,7 +1899,6 @@
/**
* Returns whether or not this Bitmap contains a Gainmap.
- * @hide
*/
public boolean hasGainmap() {
checkRecycled("Bitmap is recycled");
@@ -1908,7 +1907,6 @@
/**
* Returns the gainmap or null if the bitmap doesn't contain a gainmap
- * @hide
*/
public @Nullable Gainmap getGainmap() {
checkRecycled("Bitmap is recycled");
@@ -1919,6 +1917,14 @@
}
/**
+ * Sets a gainmap on this bitmap, or removes the gainmap if null
+ */
+ public void setGainmap(@Nullable Gainmap gainmap) {
+ checkRecycled("Bitmap is recycled");
+ nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr);
+ }
+
+ /**
* Fills the bitmap's pixels with the specified {@link Color}.
*
* @throws IllegalStateException if the bitmap is not mutable.
@@ -2403,6 +2409,7 @@
private static native void nativeSetImmutable(long nativePtr);
private static native Gainmap nativeExtractGainmap(long nativePtr);
+ private static native void nativeSetGainmap(long bitmapPtr, long gainmapPtr);
// ---------------- @CriticalNative -------------------
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index a25a605..53f23c0 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import libcore.util.NativeAllocationRegistry;
@@ -24,104 +25,288 @@
* Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
* display adjustment capability.
*
- * It is a combination of a set of metadata describing the gainmap, as well as either a 1 or 3
+ * It is a combination of a set of metadata describing how to apply the gainmap, as well as either
+ * a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
+ * (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
* channel Bitmap that represents the gainmap data itself.
*
- * @hide
+ * When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
+ * hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
+ * HDR headroom is available.
+ *
+ * <h3>Gainmap Structure</h3>
+ *
+ * The logical whole of a gainmap'd image consists of a base Bitmap that represents the original
+ * image as would be displayed without gainmap support in addition to a gainmap with a second
+ * enhancement image. In the case of a JPEG, the base image would be the typical 8-bit SDR image
+ * that the format is commonly associated with. The gainmap image is embedded alongside the base
+ * image, often at a lower resolution (such as 1/4th), along with some metadata to describe
+ * how to apply the gainmap. The gainmap image itself is then a greyscale image representing
+ * the transformation to apply onto the base image to reconstruct an HDR rendition of it.
+ *
+ * As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
+ * {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
+ * the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
+ *
+ * <h3>Applying a gainmap manually</h3>
+ *
+ * When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
+ * automatically applied. In such situations, the following steps are appropriate to render the
+ * gainmap in combination with the base image.
+ *
+ * Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
+ * this display. Let B be the pixel value from the base image in a color space that has the
+ * primaries of the base image and a linear transfer function. Let G be the pixel value from the
+ * gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
+ * as follows:
+ *
+ * First, let W be a weight parameter determining how much the gainmap will be applied.
+ * W = clamp((log(H) - log(displayRatioHdr)) /
+ * (log(displayRatioHdr) - log(displayRatioSdr), 0, 1)
+ *
+ * Next, let L be the gainmap value in log space. We compute this from the value G that was
+ * sampled from the texture as follows:
+ * L = mix(log(gainmapRatioMin), log(gainmapRatioMax), pow(G, gainmapGamma))
+ *
+ * Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
+ * compute:
+ * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr
+ * If the base image is HDR then compute:
+ * D = (B + epsilonHdr) * exp(L * (W - 1)) - epsilonSdr
+ *
+ * In the above math, log() is a natural logarithm and exp() is natural exponentiation.
*/
-public class Gainmap {
- private final long mNativePtr;
- private final Bitmap mGainmapImage;
+public final class Gainmap {
- // called from JNI and Bitmap_Delegate.
- private Gainmap(Bitmap gainmapImage, long nativeGainmap, int allocationByteCount,
- boolean fromMalloc) {
+ // Use a Holder to allow static initialization of Gainmap in the boot image.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Gainmap.class.getClassLoader(), nGetFinalizer());
+ }
+
+ final long mNativePtr;
+ private Bitmap mGainmapContents;
+
+ // called from JNI
+ private Gainmap(Bitmap gainmapContents, long nativeGainmap) {
if (nativeGainmap == 0) {
throw new RuntimeException("internal error: native gainmap is 0");
}
- mGainmapImage = gainmapImage;
+ mGainmapContents = gainmapContents;
mNativePtr = nativeGainmap;
- final NativeAllocationRegistry registry;
- if (fromMalloc) {
- registry = NativeAllocationRegistry.createMalloced(
- Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount);
- } else {
- registry = NativeAllocationRegistry.createNonmalloced(
- Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount);
- }
- registry.registerNativeAllocation(this, nativeGainmap);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativeGainmap);
}
/**
- * Returns the image data of the gainmap represented as a Bitmap
- * @return
+ * Creates a gainmap from a given Bitmap. The caller is responsible for setting the various
+ * fields to the desired values. The defaults are as follows:
+ * <ul>
+ * <li>Ratio min is 1f, 1f, 1f</li>
+ * <li>Ratio max is 2f, 2f, 2f</li>
+ * <li>Gamma is 1f, 1f, 1f</li>
+ * <li>Epsilon SDR is 0f, 0f, 0f</li>
+ * <li>Epsilon HDR is 0f, 0f, 0f</li>
+ * <li>Display ratio SDR is 1f</li>
+ * <li>Display ratio HDR is 2f</li>
+ * </ul>
+ * It is strongly recommended that at least the ratio max and display ratio HDR are adjusted
+ * to better suit the given gainmap contents.
+ */
+ public Gainmap(@NonNull Bitmap gainmapContents) {
+ this(gainmapContents, nCreateEmpty());
+ }
+
+ /**
+ * @return Returns the image data of the gainmap represented as a Bitmap. This is represented
+ * as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored
+ * such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not
+ * relevant to the gainmap's enhancement layer.
*/
@NonNull
- public Bitmap getGainmapImage() {
- return mGainmapImage;
+ public Bitmap getGainmapContents() {
+ return mGainmapContents;
}
/**
- * Sets the gainmap max metadata. For single-plane gainmaps, r, g, and b should be the same.
+ * Sets the image data of the gainmap. This is the 1 or 3 channel enhancement layer to apply
+ * to the base image. This is represented as a Bitmap for broad API compatibility, however
+ * certain aspects of the Bitmap are ignored such as {@link Bitmap#getColorSpace()} or
+ * {@link Bitmap#getGainmap()} as they are not relevant to the gainmap's enhancement layer.
+ *
+ * @param bitmap The non-null bitmap to set as the gainmap's contents
+ */
+ public void setGainmapContents(@NonNull Bitmap bitmap) {
+ // TODO: Validate here or leave native-side?
+ if (bitmap.isRecycled()) throw new IllegalArgumentException("Bitmap is recycled");
+ nSetBitmap(mNativePtr, bitmap);
+ mGainmapContents = bitmap;
+ }
+
+ /**
+ * Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
*/
@NonNull
- public void setGainmapMax(float r, float g, float b) {
- nSetGainmapMax(mNativePtr, r, g, b);
+ public void setRatioMin(float r, float g, float b) {
+ nSetRatioMin(mNativePtr, r, g, b);
}
/**
- * Gets the gainmap max metadata. For single-plane gainmaps, all 3 components should be the
+ * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
* same. The components are in r, g, b order.
*/
@NonNull
- public float[] getGainmapMax() {
+ public float[] getRatioMin() {
float[] ret = new float[3];
- nGetGainmapMax(mNativePtr, ret);
+ nGetRatioMin(mNativePtr, ret);
return ret;
}
/**
- * Sets the maximum HDR ratio for the gainmap
+ * Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
*/
@NonNull
- public void setHdrRatioMax(float max) {
- nSetHdrRatioMax(mNativePtr, max);
+ public void setRatioMax(float r, float g, float b) {
+ nSetRatioMax(mNativePtr, r, g, b);
}
/**
- * Gets the maximum HDR ratio for the gainmap
+ * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
+ * same. The components are in r, g, b order.
*/
@NonNull
- public float getHdrRatioMax() {
- return nGetHdrRatioMax(mNativePtr);
+ public float[] getRatioMax() {
+ float[] ret = new float[3];
+ nGetRatioMax(mNativePtr, ret);
+ return ret;
}
/**
- * Sets the maximum HDR ratio for the gainmap
+ * Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
*/
@NonNull
- public void setHdrRatioMin(float min) {
- nSetHdrRatioMin(mNativePtr, min);
+ public void setGamma(float r, float g, float b) {
+ nSetGamma(mNativePtr, r, g, b);
}
/**
- * Gets the maximum HDR ratio for the gainmap
+ * Gets the gainmap gamma. For single-plane gainmaps, all 3 components should be the
+ * same. The components are in r, g, b order.
*/
@NonNull
- public float getHdrRatioMin() {
- return nGetHdrRatioMin(mNativePtr);
+ public float[] getGamma() {
+ float[] ret = new float[3];
+ nGetGamma(mNativePtr, ret);
+ return ret;
+ }
+
+ /**
+ * Sets the sdr epsilon which is used to avoid numerical instability.
+ * For single-plane gainmaps, r, g, and b should be the same.
+ */
+ @NonNull
+ public void setEpsilonSdr(float r, float g, float b) {
+ nSetEpsilonSdr(mNativePtr, r, g, b);
+ }
+
+ /**
+ * Gets the sdr epsilon. For single-plane gainmaps, all 3 components should be the
+ * same. The components are in r, g, b order.
+ */
+ @NonNull
+ public float[] getEpsilonSdr() {
+ float[] ret = new float[3];
+ nGetEpsilonSdr(mNativePtr, ret);
+ return ret;
+ }
+
+ /**
+ * Sets the hdr epsilon which is used to avoid numerical instability.
+ * For single-plane gainmaps, r, g, and b should be the same.
+ */
+ @NonNull
+ public void setEpsilonHdr(float r, float g, float b) {
+ nSetEpsilonHdr(mNativePtr, r, g, b);
+ }
+
+ /**
+ * Gets the hdr epsilon. For single-plane gainmaps, all 3 components should be the
+ * same. The components are in r, g, b order.
+ */
+ @NonNull
+ public float[] getEpsilonHdr() {
+ float[] ret = new float[3];
+ nGetEpsilonHdr(mNativePtr, ret);
+ return ret;
+ }
+
+ /**
+ * Sets the hdr/sdr ratio at which point the gainmap is fully applied.
+ * @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
+ */
+ @NonNull
+ public void setDisplayRatioForFullHdr(float max) {
+ if (!Float.isFinite(max) || max < 1f) {
+ throw new IllegalArgumentException(
+ "setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
+ }
+ nSetDisplayRatioHdr(mNativePtr, max);
+ }
+
+ /**
+ * Gets the hdr/sdr ratio at which point the gainmap is fully applied.
+ */
+ @NonNull
+ public float getDisplayRatioForFullHdr() {
+ return nGetDisplayRatioHdr(mNativePtr);
+ }
+
+ /**
+ * Sets the hdr/sdr ratio below which only the SDR image is displayed.
+ * @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
+ */
+ @NonNull
+ public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
+ if (!Float.isFinite(min) || min < 1f) {
+ throw new IllegalArgumentException(
+ "setMinDisplayRatioForHdrTransition must be >= 1.0f, got = " + min);
+ }
+ nSetDisplayRatioSdr(mNativePtr, min);
+ }
+
+ /**
+ * Gets the hdr/sdr ratio below which only the SDR image is displayed.
+ */
+ @NonNull
+ public float getMinDisplayRatioForHdrTransition() {
+ return nGetDisplayRatioSdr(mNativePtr);
}
private static native long nGetFinalizer();
+ private static native long nCreateEmpty();
- private static native void nSetGainmapMax(long ptr, float r, float g, float b);
- private static native void nGetGainmapMax(long ptr, float[] components);
+ private static native void nSetBitmap(long ptr, Bitmap bitmap);
- private static native void nSetHdrRatioMax(long ptr, float max);
- private static native float nGetHdrRatioMax(long ptr);
+ private static native void nSetRatioMin(long ptr, float r, float g, float b);
+ private static native void nGetRatioMin(long ptr, float[] components);
- private static native void nSetHdrRatioMin(long ptr, float min);
- private static native float nGetHdrRatioMin(long ptr);
+ private static native void nSetRatioMax(long ptr, float r, float g, float b);
+ private static native void nGetRatioMax(long ptr, float[] components);
+
+ private static native void nSetGamma(long ptr, float r, float g, float b);
+ private static native void nGetGamma(long ptr, float[] components);
+
+ private static native void nSetEpsilonSdr(long ptr, float r, float g, float b);
+ private static native void nGetEpsilonSdr(long ptr, float[] components);
+
+ private static native void nSetEpsilonHdr(long ptr, float r, float g, float b);
+ private static native void nGetEpsilonHdr(long ptr, float[] components);
+
+ private static native void nSetDisplayRatioHdr(long ptr, float max);
+ private static native float nGetDisplayRatioHdr(long ptr);
+
+ private static native void nSetDisplayRatioSdr(long ptr, float min);
+ private static native float nGetDisplayRatioSdr(long ptr);
}
diff --git a/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
index 7475aba..b2d5939 100644
--- a/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
+++ b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
@@ -18,6 +18,20 @@
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <set>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleX"
+ android:valueTo="1.0"
+ android:valueType="floatType"/>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleY"
+ android:valueTo="1.0"
+ android:valueType="floatType"/>
+ </set>
+ </item>
<item android:state_focused="true">
<set>
<objectAnimator
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index b085b73..34bf9e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -76,6 +76,7 @@
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
+ leash.setUnreleasedWarningCallSite("RootDisplayAreaOrganizer.onDisplayAreaAppeared");
mDisplayAreasInfo.put(displayId, displayAreaInfo);
mLeashes.put(displayId, leash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index ca977ed..544d757 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -122,6 +122,8 @@
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
+ leash.setUnreleasedWarningCallSite(
+ "RootTaskDisplayAreaOrganizer.onDisplayAreaAppeared");
mDisplayAreasInfo.put(displayId, displayAreaInfo);
mLeashes.put(displayId, leash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b5ef72a..585f81c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -464,6 +464,9 @@
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (leash != null) {
+ leash.setUnreleasedWarningCallSite("ShellTaskOrganizer.onTaskAppeared");
+ }
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index b8e8363..d6e1a82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -25,7 +25,6 @@
import static android.util.RotationUtils.rotateBounds;
import static android.util.RotationUtils.rotateInsets;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -46,9 +45,9 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.Surface;
+import android.view.WindowInsets;
import androidx.annotation.VisibleForTesting;
@@ -372,23 +371,20 @@
// Only navigation bar
if (hasNavigationBar) {
- final InsetsSource extraNavBar = insetsState.peekSource(ITYPE_EXTRA_NAVIGATION_BAR);
- final boolean hasExtraNav = extraNavBar != null && extraNavBar.isVisible();
+ final Insets insets = insetsState.calculateInsets(
+ insetsState.getDisplayFrame(),
+ WindowInsets.Type.navigationBars(),
+ false /* ignoreVisibility */);
+ outInsets.set(insets.left, insets.top, insets.right, insets.bottom);
int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
int navBarSize =
getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
if (position == NAV_BAR_BOTTOM) {
- outInsets.bottom = hasExtraNav
- ? Math.max(navBarSize, extraNavBar.getFrame().height())
- : navBarSize;
+ outInsets.bottom = Math.max(outInsets.bottom , navBarSize);
} else if (position == NAV_BAR_RIGHT) {
- outInsets.right = hasExtraNav
- ? Math.max(navBarSize, extraNavBar.getFrame().width())
- : navBarSize;
+ outInsets.right = Math.max(outInsets.right , navBarSize);
} else if (position == NAV_BAR_LEFT) {
- outInsets.left = hasExtraNav
- ? Math.max(navBarSize, extraNavBar.getFrame().width())
- : navBarSize;
+ outInsets.left = Math.max(outInsets.left , navBarSize);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 5e46023..5e42782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -223,7 +223,7 @@
}
final Display display = mDisplayController.getDisplay(mDisplayId);
SurfaceControlViewHost viewRoot =
- new SurfaceControlViewHost(view.getContext(), display, wwm);
+ new SurfaceControlViewHost(view.getContext(), display, wwm, "SystemWindows");
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index fa3a6ad..e44257e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -114,7 +114,8 @@
context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
null /* options */);
mHostLeash = rootLeash;
- mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this);
+ mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this,
+ "SplitDecorManager");
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 6b5ddcb..eb3c1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -113,7 +113,8 @@
"Try to inflate divider view again without release first");
}
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
+ "SplitWindowManager");
mDividerView = (DividerView) LayoutInflater.from(mContext)
.inflate(R.layout.split_divider, null /* root */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 34e650a..efd4594 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -363,7 +363,8 @@
/** Creates a {@link SurfaceControlViewHost} for this window manager. */
@VisibleForTesting(visibility = PRIVATE)
public SurfaceControlViewHost createSurfaceViewHost() {
- return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
+ getClass().getSimpleName());
}
/** Gets the layout params. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index f376e1f..32894cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -117,6 +117,7 @@
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
+ leash.setUnreleasedWarningCallSite("HideDisplayCutoutOrganizer.onDisplayAreaAppeared");
if (!addDisplayAreaInfoAndLeashToMap(displayAreaInfo, leash)) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index 8ebcd81..71cc8df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -122,7 +122,8 @@
return false;
}
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
+ "BackgroundWindowManager");
mBackgroundView = (View) LayoutInflater.from(mContext)
.inflate(R.layout.background_panel, null /* root */);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 451afa0..38ce164 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -154,6 +154,8 @@
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
+ leash.setUnreleasedWarningCallSite(
+ "OneHandedSiaplyAreaOrganizer.onDisplayAreaAppeared");
mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 111cfd8..f11836e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -650,7 +650,6 @@
}
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
- mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
// If the displayId of the task is different than what PipBoundsHandler has, then update
// it. This is possible if we entered PiP on an external display.
@@ -659,6 +658,17 @@
mOnDisplayIdChangeCallback.accept(info.displayId);
}
+ // UiEvent logging.
+ final PipUiEventLogger.PipUiEventEnum uiEventEnum;
+ if (isLaunchIntoPipTask()) {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER_CONTENT_PIP;
+ } else if (mPipTransitionState.getInSwipePipToHomeTransition()) {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER;
+ } else {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER;
+ }
+ mPipUiEventLoggerLogger.log(uiEventEnum);
+
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index 513ebba..3e5a19b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -78,6 +78,12 @@
@UiEvent(doc = "Activity enters picture-in-picture mode")
PICTURE_IN_PICTURE_ENTER(603),
+ @UiEvent(doc = "Activity enters picture-in-picture mode with auto-enter-pip API")
+ PICTURE_IN_PICTURE_AUTO_ENTER(1313),
+
+ @UiEvent(doc = "Activity enters picture-in-picture mode from content-pip API")
+ PICTURE_IN_PICTURE_ENTER_CONTENT_PIP(1314),
+
@UiEvent(doc = "Expands from picture-in-picture to fullscreen")
PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index ec34f73..00ed094 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1215,6 +1215,9 @@
@Override
public void stopSwipePipToHome(int taskId, ComponentName componentName,
Rect destinationBounds, SurfaceControl overlay) {
+ if (overlay != null) {
+ overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
+ }
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> {
controller.stopSwipePipToHome(taskId, componentName, destinationBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 02b9197..a437a3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -740,7 +740,7 @@
if (mRegistered) return;
mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
- mMainHandler);
+ mMainHandler, Context.RECEIVER_NOT_EXPORTED);
mRegistered = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 1549355..5a5ceab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -142,9 +142,12 @@
Animation a = null;
if (animAttr != 0) {
if (overrideType == ANIM_FROM_STYLE && !isTask) {
- a = transitionAnimation
- .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
- animAttr, translucent);
+ a = loadCustomActivityTransition(animAttr, options, enter, transitionAnimation);
+ if (a == null) {
+ a = transitionAnimation
+ .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
+ animAttr, translucent);
+ }
} else {
a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
@@ -157,6 +160,37 @@
return a;
}
+ static Animation loadCustomActivityTransition(int animAttr,
+ TransitionInfo.AnimationOptions options, boolean enter,
+ TransitionAnimation transitionAnimation) {
+ Animation a = null;
+ boolean isOpen = false;
+ switch (animAttr) {
+ case R.styleable.WindowAnimation_activityOpenEnterAnimation:
+ case R.styleable.WindowAnimation_activityOpenExitAnimation:
+ isOpen = true;
+ break;
+ case R.styleable.WindowAnimation_activityCloseEnterAnimation:
+ case R.styleable.WindowAnimation_activityCloseExitAnimation:
+ break;
+ default:
+ return null;
+ }
+
+ final TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim =
+ options.getCustomActivityTransition(isOpen);
+ if (transitionAnim != null) {
+ a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
+ enter ? transitionAnim.getCustomEnterResId()
+ : transitionAnim.getCustomExitResId());
+ if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
+ a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
+ }
+ }
+
+ return a;
+ }
+
/**
* Gets the background {@link ColorInt} for the given transition animation if it is set.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ae685ad..91b0aa1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -484,7 +484,7 @@
interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
- return new SurfaceControlViewHost(c, d, wmm);
+ return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 05fd889..e8784d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -18,7 +18,6 @@
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -333,7 +332,8 @@
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
InsetsState insetsState = new InsetsState();
- InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
insetsSource.setFrame(0, 0, 1000, 1000);
insetsState.addSource(insetsSource);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index f3f615d9..4de5298 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,7 +20,6 @@
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -338,8 +337,10 @@
// Update if the insets change on the existing display layout
clearInvocations(mWindowManager);
InsetsState insetsState = new InsetsState();
- InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
- insetsSource.setFrame(0, 0, 1000, 1000);
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, 1800, 1000, 2000);
insetsState.addSource(insetsSource);
displayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager.updateDisplayLayout(displayLayout);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 9e3f115..03d89cc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -396,8 +396,6 @@
"libharfbuzz_ng",
"libimage_io",
"libjpeg",
- "libjpegdecoder",
- "libjpegencoder",
"libjpegrecoverymap",
"liblog",
"libminikin",
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 6d7c727..8266beb 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -17,13 +17,25 @@
#include "ImageDecoder.h"
#include <Gainmap.h>
+#include <SkAlphaType.h>
#include <SkAndroidCodec.h>
#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
+#include <SkCodec.h>
+#include <SkCodecAnimation.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
#include <SkEncodedOrigin.h>
+#include <SkImageInfo.h>
#include <SkGainmapInfo.h>
+#include <SkMatrix.h>
#include <SkPaint.h>
+#include <SkPngChunkReader.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkSamplingOptions.h>
+#include <SkSize.h>
#include <SkStream.h>
#include <hwui/Bitmap.h>
#include <log/log.h>
@@ -506,6 +518,9 @@
decoder.mOverrideOrigin.emplace(getOrigin());
// Update mDecodeSize / mTargetSize for the overridden origin
decoder.setTargetSize(decoder.width(), decoder.height());
+ if (decoder.gray()) {
+ decoder.setOutColorType(kGray_8_SkColorType);
+ }
const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height();
@@ -528,6 +543,9 @@
}
SkImageInfo bitmapInfo = decoder.getOutputInfo();
+ if (bitmapInfo.colorType() == kGray_8_SkColorType) {
+ bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+ }
SkBitmap bm;
if (!bm.setInfo(bitmapInfo)) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 10c287d..e71a2a5 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,6 @@
#undef LOG_TAG
#define LOG_TAG "Bitmap"
+// #define LOG_NDEBUG 0
#include "Bitmap.h"
#include <hwui/Bitmap.h>
@@ -372,15 +373,33 @@
return srcPM.readPixels(dstPM);
}
-static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
- jint dstConfigHandle, jboolean isMutable) {
+static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle,
+ jboolean isMutable) {
+ LocalScopedBitmap bitmapHolder(srcHandle);
+ if (!bitmapHolder.valid()) {
+ return NULL;
+ }
+ const Bitmap& original = bitmapHolder->bitmap();
+ const bool hasGainmap = original.hasGainmap();
SkBitmap src;
- reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
+ bitmapHolder->getSkBitmap(&src);
+
if (dstConfigHandle == GraphicsJNI::hardwareLegacyBitmapConfig()) {
sk_sp<Bitmap> bitmap(Bitmap::allocateHardwareBitmap(src));
if (!bitmap.get()) {
return NULL;
}
+ if (hasGainmap) {
+ auto gainmap = sp<uirenderer::Gainmap>::make();
+ gainmap->info = original.gainmap()->info;
+ const SkBitmap skSrcBitmap = original.gainmap()->bitmap->getSkBitmap();
+ sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
+ if (!skBitmap.get()) {
+ return NULL;
+ }
+ gainmap->bitmap = std::move(skBitmap);
+ bitmap->setGainmap(std::move(gainmap));
+ }
return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
@@ -392,6 +411,18 @@
return NULL;
}
auto bitmap = allocator.getStorageObjAndReset();
+ if (hasGainmap) {
+ auto gainmap = sp<uirenderer::Gainmap>::make();
+ gainmap->info = original.gainmap()->info;
+ const SkBitmap skSrcBitmap = original.gainmap()->bitmap->getSkBitmap();
+ SkBitmap skDestBitmap;
+ HeapAllocator destAllocator;
+ if (!bitmapCopyTo(&skDestBitmap, dstCT, skSrcBitmap, &destAllocator)) {
+ return NULL;
+ }
+ gainmap->bitmap = sk_sp<Bitmap>(destAllocator.getStorageObjAndReset());
+ bitmap->setGainmap(std::move(gainmap));
+ }
return createBitmap(env, bitmap, getPremulBitmapCreateFlags(isMutable));
}
@@ -1269,6 +1300,13 @@
return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap());
}
+static void Bitmap_setGainmap(JNIEnv*, jobject, jlong bitmapHandle, jlong gainmapPtr) {
+ LocalScopedBitmap bitmapHolder(bitmapHandle);
+ if (!bitmapHolder.valid()) return;
+ uirenderer::Gainmap* gainmap = reinterpret_cast<uirenderer::Gainmap*>(gainmapPtr);
+ bitmapHolder->bitmap().setGainmap(sp<uirenderer::Gainmap>::fromExisting(gainmap));
+}
+
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gBitmapMethods[] = {
@@ -1320,6 +1358,7 @@
{"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
{"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
{"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap},
+ {"nativeSetGainmap", "(JJ)V", (void*)Bitmap_setGainmap},
// ------------ @CriticalNative ----------------
{"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable},
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index f2efbc7..9cd3fb0 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -45,20 +45,18 @@
jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap) {
auto gainmap = bitmap.gainmap();
jobject jGainmapImage;
- size_t allocationSize;
{
// Scope to guard the release of nativeBitmap
auto nativeBitmap = gainmap->bitmap;
const int createFlags = getCreateFlags(nativeBitmap);
- allocationSize = nativeBitmap->getAllocationByteCount();
jGainmapImage = bitmap::createBitmap(env, nativeBitmap.release(), createFlags);
}
// Grab a ref for the jobject
gainmap->incStrong(0);
jobject obj = env->NewObject(gGainmap_class, gGainmap_constructorMethodID, jGainmapImage,
- gainmap.get(), allocationSize + sizeof(Gainmap), true);
+ gainmap.get());
if (env->ExceptionCheck() != 0) {
// sadtrombone
@@ -77,47 +75,109 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Gainmap_destructor));
}
-static void Gainmap_setGainmapMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
- jfloat b) {
- fromJava(gainmapPtr)->info.fLogRatioMax = {r, g, b, 1.f};
+jlong Gainmap_createEmpty(JNIEnv*, jobject) {
+ Gainmap* gainmap = new Gainmap();
+ gainmap->incStrong(0);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
}
-static void Gainmap_getGainmapMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
- const auto ratioMax = fromJava(gainmapPtr)->info.fLogRatioMax;
- jfloat buf[3]{ratioMax.fR, ratioMax.fG, ratioMax.fB};
+static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
+ android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
+ fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
+}
+
+static void Gainmap_setRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+ fromJava(gainmapPtr)->info.fGainmapRatioMin = {r, g, b, 1.f};
+}
+
+static void Gainmap_getRatioMin(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMin;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
env->SetFloatArrayRegion(components, 0, 3, buf);
}
-static void Gainmap_setHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
- fromJava(gainmapPtr)->info.fHdrRatioMax = max;
+static void Gainmap_setRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+ fromJava(gainmapPtr)->info.fGainmapRatioMax = {r, g, b, 1.f};
}
-static jfloat Gainmap_getHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr) {
- return fromJava(gainmapPtr)->info.fHdrRatioMax;
+static void Gainmap_getRatioMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMax;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
}
-static void Gainmap_setHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
- fromJava(gainmapPtr)->info.fHdrRatioMin = min;
+static void Gainmap_setGamma(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+ fromJava(gainmapPtr)->info.fGainmapGamma = {r, g, b, 1.f};
}
-static jfloat Gainmap_getHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr) {
- return fromJava(gainmapPtr)->info.fHdrRatioMin;
+static void Gainmap_getGamma(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fGainmapGamma;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setEpsilonSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
+ jfloat b) {
+ fromJava(gainmapPtr)->info.fEpsilonSdr = {r, g, b, 1.f};
+}
+
+static void Gainmap_getEpsilonSdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fEpsilonSdr;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setEpsilonHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
+ jfloat b) {
+ fromJava(gainmapPtr)->info.fEpsilonHdr = {r, g, b, 1.f};
+}
+
+static void Gainmap_getEpsilonHdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fEpsilonHdr;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
+ fromJava(gainmapPtr)->info.fDisplayRatioHdr = max;
+}
+
+static jfloat Gainmap_getDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr) {
+ return fromJava(gainmapPtr)->info.fDisplayRatioHdr;
+}
+
+static void Gainmap_setDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
+ fromJava(gainmapPtr)->info.fDisplayRatioSdr = min;
+}
+
+static jfloat Gainmap_getDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr) {
+ return fromJava(gainmapPtr)->info.fDisplayRatioSdr;
}
static const JNINativeMethod gGainmapMethods[] = {
{"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
- {"nSetGainmapMax", "(JFFF)V", (void*)Gainmap_setGainmapMax},
- {"nGetGainmapMax", "(J[F)V", (void*)Gainmap_getGainmapMax},
- {"nSetHdrRatioMax", "(JF)V", (void*)Gainmap_setHdrRatioMax},
- {"nGetHdrRatioMax", "(J)F", (void*)Gainmap_getHdrRatioMax},
- {"nSetHdrRatioMin", "(JF)V", (void*)Gainmap_setHdrRatioMin},
- {"nGetHdrRatioMin", "(J)F", (void*)Gainmap_getHdrRatioMin},
+ {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
+ {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
+ {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
+ {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
+ {"nSetRatioMax", "(JFFF)V", (void*)Gainmap_setRatioMax},
+ {"nGetRatioMax", "(J[F)V", (void*)Gainmap_getRatioMax},
+ {"nSetGamma", "(JFFF)V", (void*)Gainmap_setGamma},
+ {"nGetGamma", "(J[F)V", (void*)Gainmap_getGamma},
+ {"nSetEpsilonSdr", "(JFFF)V", (void*)Gainmap_setEpsilonSdr},
+ {"nGetEpsilonSdr", "(J[F)V", (void*)Gainmap_getEpsilonSdr},
+ {"nSetEpsilonHdr", "(JFFF)V", (void*)Gainmap_setEpsilonHdr},
+ {"nGetEpsilonHdr", "(J[F)V", (void*)Gainmap_getEpsilonHdr},
+ {"nSetDisplayRatioHdr", "(JF)V", (void*)Gainmap_setDisplayRatioHdr},
+ {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr},
+ {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr},
+ {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr},
};
int register_android_graphics_Gainmap(JNIEnv* env) {
gGainmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Gainmap"));
gGainmap_constructorMethodID =
- GetMethodIDOrDie(env, gGainmap_class, "<init>", "(Landroid/graphics/Bitmap;JIZ)V");
+ GetMethodIDOrDie(env, gGainmap_class, "<init>", "(Landroid/graphics/Bitmap;J)V");
return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods,
NELEM(gGainmapMethods));
}
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index add62b1..fda7080 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -18,11 +18,16 @@
#include <FrontBufferedStream.h>
#include <HardwareBitmapUploader.h>
+#include <SkAlphaType.h>
#include <SkAndroidCodec.h>
#include <SkBitmap.h>
+#include <SkCodec.h>
+#include <SkCodecAnimation.h>
#include <SkColorSpace.h>
+#include <SkColorType.h>
#include <SkImageInfo.h>
#include <SkRect.h>
+#include <SkSize.h>
#include <SkStream.h>
#include <SkString.h>
#include <androidfw/Asset.h>
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 80bca1f..6c070fe 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -238,7 +238,7 @@
}
///////////////////////////////////////////////////////////////////////////////
-using namespace android::recoverymap;
+using namespace android::jpegrecoverymap;
jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
switch (aDataSpace & ADataSpace::STANDARD_MASK) {
@@ -294,7 +294,7 @@
return false;
}
- RecoveryMap recoveryMap;
+ JpegR jpegREncoder;
jpegr_uncompressed_struct p010;
p010.data = hdr;
@@ -314,7 +314,7 @@
std::unique_ptr<uint8_t[]> jpegr_data = std::make_unique<uint8_t[]>(jpegR.maxLength);
jpegR.data = jpegr_data.get();
- if (int success = recoveryMap.encodeJPEGR(&p010, &yuv420,
+ if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
hdrTransferFunction,
&jpegR, jpegQuality, nullptr); success != android::OK) {
ALOGW("Encode JPEG/R failed, error code: %d.", success);
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 3d6d1f3..d22a26c 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,7 +2,7 @@
#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
#include <android/data_space.h>
-#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegr.h>
extern "C" {
#include "jpeglib.h"
@@ -77,7 +77,7 @@
class P010Yuv420ToJpegREncoder {
public:
/** Encode YUV data to jpeg/r, which is output to a stream.
- * This method will call RecoveryMap::EncodeJPEGR() method. If encoding failed,
+ * This method will call JpegR::EncodeJPEGR() method. If encoding failed,
* Corresponding error code (defined in jpegrerrorcode.h) will be printed and this
* method will be terminated and return false.
*
@@ -103,7 +103,7 @@
* @param aDataSpace data space defined in data_space.h.
* @return color gamut for JPEG/R.
*/
- static android::recoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace);
+ static android::jpegrecoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace);
/** Map data space (defined in DataSpace.java and data_space.h) to the transfer function
* used in JPEG/R
@@ -112,7 +112,7 @@
* @param aDataSpace data space defined in data_space.h.
* @return color gamut for JPEG/R.
*/
- static android::recoverymap::jpegr_transfer_function findHdrTransferFunction(
+ static android::jpegrecoverymap::jpegr_transfer_function findHdrTransferFunction(
JNIEnv* env, int aDataSpace);
};
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index c1bcb55..00919dc 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -176,14 +176,13 @@
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
- if (mInitialized && mBlobCache && mSavePending) {
+ if (mInitialized && mBlobCache) {
if (mIDHash.size()) {
auto key = sIDKey;
set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
}
mBlobCache->writeToFile();
}
- mSavePending = false;
}
void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
@@ -226,10 +225,10 @@
}
set(bc, key.data(), keySize, value, valueSize);
- if (!mSavePending && mDeferredSaveDelay > 0) {
+ if (!mSavePending && mDeferredSaveDelayMs > 0) {
mSavePending = true;
std::thread deferredSaveThread([this]() {
- sleep(mDeferredSaveDelay);
+ usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds
std::lock_guard<std::mutex> lock(mMutex);
// Store file on disk if there a new shader or Vulkan pipeline cache size changed.
if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
@@ -238,6 +237,7 @@
mTryToStorePipelineCache = false;
mCacheDirty = false;
}
+ mSavePending = false;
});
deferredSaveThread.detach();
}
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index bc35fa5f..f5506d6 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -156,7 +156,8 @@
* pending. Each time a key/value pair is inserted into the cache via
* load, a deferred save is initiated if one is not already pending.
* This will wait some amount of time and then trigger a save of the cache
- * contents to disk.
+ * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
+ * is disabled.
*/
bool mSavePending = false;
@@ -166,9 +167,11 @@
size_t mObservedBlobValueSize = 20 * 1024;
/**
- * The time in seconds to wait before saving newly inserted cache entries.
+ * The time in milliseconds to wait before saving newly inserted cache entries.
+ *
+ * WARNING: setting this to 0 will disable writing the cache to disk.
*/
- unsigned int mDeferredSaveDelay = 4;
+ unsigned int mDeferredSaveDelayMs = 4 * 1000;
/**
* "mMutex" is the mutex used to prevent concurrent access to the member
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 576e946..7bcd45c 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#include <GrDirectContext.h>
+#include <Properties.h>
+#include <SkData.h>
+#include <SkRefCnt.h>
#include <cutils/properties.h>
#include <dirent.h>
#include <errno.h>
@@ -22,11 +26,12 @@
#include <stdlib.h>
#include <sys/types.h>
#include <utils/Log.h>
+
#include <cstdint>
+
#include "FileBlobCache.h"
#include "pipeline/skia/ShaderCache.h"
-#include <SkData.h>
-#include <SkRefCnt.h>
+#include "tests/common/TestUtils.h"
using namespace android::uirenderer::skiapipeline;
@@ -37,11 +42,38 @@
class ShaderCacheTestUtils {
public:
/**
- * "setSaveDelay" sets the time in seconds to wait before saving newly inserted cache entries.
- * If set to 0, then deferred save is disabled.
+ * Hack to reset all member variables of the given cache to their default / initial values.
+ *
+ * WARNING: this must be kept up to date manually, since ShaderCache's parent disables just
+ * reassigning a new instance.
*/
- static void setSaveDelay(ShaderCache& cache, unsigned int saveDelay) {
- cache.mDeferredSaveDelay = saveDelay;
+ static void reinitializeAllFields(ShaderCache& cache) {
+ ShaderCache newCache = ShaderCache();
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ // By order of declaration
+ cache.mInitialized = newCache.mInitialized;
+ cache.mBlobCache.reset(nullptr);
+ cache.mFilename = newCache.mFilename;
+ cache.mIDHash.clear();
+ cache.mSavePending = newCache.mSavePending;
+ cache.mObservedBlobValueSize = newCache.mObservedBlobValueSize;
+ cache.mDeferredSaveDelayMs = newCache.mDeferredSaveDelayMs;
+ cache.mTryToStorePipelineCache = newCache.mTryToStorePipelineCache;
+ cache.mInStoreVkPipelineInProgress = newCache.mInStoreVkPipelineInProgress;
+ cache.mNewPipelineCacheSize = newCache.mNewPipelineCacheSize;
+ cache.mOldPipelineCacheSize = newCache.mOldPipelineCacheSize;
+ cache.mCacheDirty = newCache.mCacheDirty;
+ cache.mNumShadersCachedInRam = newCache.mNumShadersCachedInRam;
+ }
+
+ /**
+ * "setSaveDelayMs" sets the time in milliseconds to wait before saving newly inserted cache
+ * entries. If set to 0, then deferred save is disabled, and "saveToDiskLocked" must be called
+ * manually, as seen in the "terminate" testing helper function.
+ */
+ static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ cache.mDeferredSaveDelayMs = saveDelayMs;
}
/**
@@ -50,8 +82,9 @@
*/
static void terminate(ShaderCache& cache, bool saveContent) {
std::lock_guard<std::mutex> lock(cache.mMutex);
- cache.mSavePending = saveContent;
- cache.saveToDiskLocked();
+ if (saveContent) {
+ cache.saveToDiskLocked();
+ }
cache.mBlobCache = NULL;
}
@@ -62,6 +95,38 @@
static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
return cache.validateCache(hash.data(), hash.size() * sizeof(T));
}
+
+ /**
+ * Waits until cache::mSavePending is false, checking every 0.1 ms *while the mutex is free*.
+ *
+ * Fails if there was no save pending, or if the cache was already being written to disk, or if
+ * timeoutMs is exceeded.
+ *
+ * Note: timeoutMs only guards against mSavePending getting stuck like in b/268205519, and
+ * cannot protect against mutex-based deadlock. Reaching timeoutMs implies something is broken,
+ * so setting it to a sufficiently large value will not delay execution in the happy state.
+ */
+ static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
+ {
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ ASSERT_TRUE(cache.mSavePending);
+ }
+ bool saving = true;
+ float elapsedMilliseconds = 0;
+ while (saving) {
+ if (elapsedMilliseconds >= timeoutMs) {
+ FAIL() << "Timed out after waiting " << timeoutMs << " ms for a pending save";
+ }
+ // This small (0.1 ms) delay is to avoid working too much while waiting for
+ // deferredSaveThread to take the mutex and start the disk write.
+ const int delayMicroseconds = 100;
+ usleep(delayMicroseconds);
+ elapsedMilliseconds += (float)delayMicroseconds / 1000;
+
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ saving = cache.mSavePending;
+ }
+ }
};
} /* namespace skiapipeline */
@@ -83,6 +148,18 @@
return false;
}
+/**
+ * Attempts to delete the given file, and asserts that either:
+ * 1. Deletion was successful, OR
+ * 2. The file did not exist.
+ *
+ * Tip: wrap calls to this in ASSERT_NO_FATAL_FAILURE(x) if a test should exit early if this fails.
+ */
+void deleteFileAssertSuccess(const std::string& filePath) {
+ int deleteResult = remove(filePath.c_str());
+ ASSERT_TRUE(0 == deleteResult || ENOENT == errno);
+}
+
inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) {
return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() &&
0 == memcmp(shader1->data(), shader2->data(), shader1->size());
@@ -93,6 +170,10 @@
return checkShader(shader, shader2);
}
+inline bool checkShader(const sk_sp<SkData>& shader, const std::string& program) {
+ return checkShader(shader, program.c_str());
+}
+
template <typename T>
bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) {
sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T));
@@ -103,6 +184,10 @@
shader = SkData::MakeWithCString(program);
}
+void setShader(sk_sp<SkData>& shader, const std::string& program) {
+ setShader(shader, program.c_str());
+}
+
template <typename T>
void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) {
shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T));
@@ -126,13 +211,13 @@
std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
// remove any test files from previous test run
- int deleteFile = remove(cacheFile1.c_str());
- ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
std::srand(0);
// read the cache from a file that does not exist
ShaderCache::get().setFilename(cacheFile1.c_str());
- ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save
+ ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
ShaderCache::get().initShaderDiskCache();
// read a key - should not be found since the cache is empty
@@ -186,7 +271,8 @@
ASSERT_TRUE(checkShader(outVS2, dataBuffer));
ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
- remove(cacheFile1.c_str());
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
}
TEST(ShaderCacheTest, testCacheValidation) {
@@ -198,13 +284,13 @@
std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
// remove any test files from previous test run
- int deleteFile = remove(cacheFile1.c_str());
- ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
std::srand(0);
// generate identity and read the cache from a file that does not exist
ShaderCache::get().setFilename(cacheFile1.c_str());
- ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save
+ ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
std::vector<uint8_t> identity(1024);
genRandomData(identity);
ShaderCache::get().initShaderDiskCache(
@@ -278,7 +364,81 @@
}
ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
- remove(cacheFile1.c_str());
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
+}
+
+using namespace android::uirenderer;
+RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+ if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
+ // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+ GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
+ }
+ if (!folderExist(getExternalStorageFolder())) {
+ // Don't run the test if external storage folder is not available
+ return;
+ }
+ std::string cacheFile = getExternalStorageFolder() + "/shaderCacheTest";
+ GrDirectContext* grContext = renderThread.getGrContext();
+
+ // Remove any test files from previous test run
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
+
+ // The first iteration of this loop is to save an initial VkPipelineCache data blob to disk,
+ // which sets up the second iteration for a common scenario of comparing a "new" VkPipelineCache
+ // blob passed to "store" against the same blob that's already in the persistent cache from a
+ // previous launch. "reinitializeAllFields" is critical to emulate each iteration being as close
+ // to the state of a freshly launched app as possible, as the initial values of member variables
+ // like mInStoreVkPipelineInProgress and mOldPipelineCacheSize are critical to catch issues
+ // such as b/268205519
+ for (int flushIteration = 1; flushIteration <= 2; flushIteration++) {
+ SCOPED_TRACE("Frame flush iteration " + std::to_string(flushIteration));
+ // Reset *all* in-memory data and reload the cache from disk.
+ ShaderCacheTestUtils::reinitializeAllFields(ShaderCache::get());
+ ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 10); // Delay must be > 0 to save.
+ ShaderCache::get().setFilename(cacheFile.c_str());
+ ShaderCache::get().initShaderDiskCache();
+
+ // 1st iteration: store pipeline data to be read back on a subsequent "boot" of the "app".
+ // 2nd iteration: ensure that an initial frame flush (without storing any shaders) given the
+ // same pipeline data that's already on disk doesn't break the cache.
+ ShaderCache::get().onVkFrameFlushed(grContext);
+ ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
+ }
+
+ constexpr char shader1[] = "sassas";
+ constexpr char shader2[] = "someVS";
+ constexpr int numIterations = 3;
+ // Also do n iterations of separate "store some shaders then flush the frame" pairs to just
+ // double-check the cache also doesn't get stuck from that use case.
+ for (int saveIteration = 1; saveIteration <= numIterations; saveIteration++) {
+ SCOPED_TRACE("Shader save iteration " + std::to_string(saveIteration));
+ // Write twice to the in-memory cache, which should start a deferred save with both queued.
+ sk_sp<SkData> inVS;
+ setShader(inVS, shader1 + std::to_string(saveIteration));
+ ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
+ setShader(inVS, shader2 + std::to_string(saveIteration));
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
+
+ // Simulate flush to also save latest pipeline info.
+ ShaderCache::get().onVkFrameFlushed(grContext);
+ ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
+ }
+
+ // Reload from disk to ensure saving succeeded.
+ ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+ ShaderCache::get().initShaderDiskCache();
+
+ // Read twice, ensure equal to last store.
+ sk_sp<SkData> outVS;
+ ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
+ ASSERT_TRUE(checkShader(outVS, shader1 + std::to_string(numIterations)));
+ ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
+ ASSERT_TRUE(checkShader(outVS, shader2 + std::to_string(numIterations)));
+
+ // Clean up.
+ ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
}
} // namespace
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7de3abc3..5a03ad3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7625,22 +7625,13 @@
return codecConfigList;
}
- /**
- * Returns a list of audio formats that corresponds to encoding formats
- * supported on offload path for Le audio playback.
- *
- * @return a list of {@link BluetoothLeAudioCodecConfig} objects containing encoding formats
- * supported for offload Le Audio playback
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- @NonNull
- public List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio() {
+ private List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio(
+ @AudioSystem.BtOffloadDeviceType int deviceType) {
ArrayList<Integer> formatsList = new ArrayList<>();
ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList = new ArrayList<>();
int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(
- AudioSystem.DEVICE_OUT_BLE_HEADSET, formatsList);
+ deviceType, formatsList);
if (status != AudioManager.SUCCESS) {
Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForLeAudio failed:" + status);
return leAudioCodecConfigList;
@@ -7657,6 +7648,34 @@
return leAudioCodecConfigList;
}
+ /**
+ * Returns a list of audio formats that corresponds to encoding formats
+ * supported on offload path for Le audio playback.
+ *
+ * @return a list of {@link BluetoothLeAudioCodecConfig} objects containing encoding formats
+ * supported for offload Le Audio playback
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio() {
+ return getHwOffloadFormatsSupportedForLeAudio(AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ }
+
+ /**
+ * Returns a list of audio formats that corresponds to encoding formats
+ * supported on offload path for Le Broadcast playback.
+ *
+ * @return a list of {@link BluetoothLeAudioCodecConfig} objects containing encoding formats
+ * supported for offload Le Broadcast playback
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeBroadcast() {
+ return getHwOffloadFormatsSupportedForLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
+ }
+
// Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
// (unpredictable) last time updateAudioPortCache() was called by someone, keep a list
// of the ports that exist at the time of the last notification.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 9339c3d..293d3d2 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -273,10 +273,11 @@
/** @hide */
@IntDef(flag = false, prefix = "DEVICE_", value = {
DEVICE_OUT_BLUETOOTH_A2DP,
- DEVICE_OUT_BLE_HEADSET}
+ DEVICE_OUT_BLE_HEADSET,
+ DEVICE_OUT_BLE_BROADCAST}
)
@Retention(RetentionPolicy.SOURCE)
- public @interface DeviceType {}
+ public @interface BtOffloadDeviceType {}
/**
* @hide
@@ -1970,7 +1971,7 @@
* Returns a list of audio formats (codec) supported on the A2DP and LE audio offload path.
*/
public static native int getHwOffloadFormatsSupportedForBluetoothMedia(
- @DeviceType int deviceType, ArrayList<Integer> formatList);
+ @BtOffloadDeviceType int deviceType, ArrayList<Integer> formatList);
/** @hide */
public static native int setSurroundFormatEnabled(int audioFormat, boolean enabled);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 6d65c26..f327e4e 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.Surface;
import java.util.Map;
@@ -74,8 +75,9 @@
* Returns an {@link Intent} that <b>must</b> be passed to
* {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
* capture. The activity will prompt the user whether to allow screen capture. The result of
- * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)}
- * should be passed to {@link #getMediaProjection(int, Intent)}.
+ * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)
+ * onActivityResult(int, int, Intent)}) should be passed to
+ * {@link #getMediaProjection(int, Intent)}.
* <p>
* Identical to calling {@link #createScreenCaptureIntent(MediaProjectionConfig)} with
* a {@link MediaProjectionConfig#createConfigForUserChoice()}.
@@ -102,8 +104,8 @@
* capture. Customizes the activity and resulting {@link MediaProjection} session based up
* the provided {@code config}. The activity will prompt the user whether to allow screen
* capture. The result of this activity (received by overriding
- * {@link Activity#onActivityResult(int, int, Intent)}) should be passed to
- * {@link #getMediaProjection(int, Intent)}.
+ * {@link Activity#onActivityResult(int, int, Intent) onActivityResult(int, int, Intent)})
+ * should be passed to {@link #getMediaProjection(int, Intent)}.
*
* <p>
* If {@link MediaProjectionConfig} was created from:
@@ -146,47 +148,48 @@
/**
* Retrieves the {@link MediaProjection} obtained from a successful screen
- * capture request. The result code and data from the request are provided
- * by overriding {@link Activity#onActivityResult(int, int, Intent)
- * onActivityResult(int, int, Intent)}, which is called after starting an
- * activity using {@link #createScreenCaptureIntent()}.
- *
- * <p>Starting from Android {@link android.os.Build.VERSION_CODES#R}, if
- * your application requests the
- * {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW
- * SYSTEM_ALERT_WINDOW} permission, and the user has not explicitly denied
- * it, the permission will be automatically granted until the projection is
- * stopped. The permission allows your app to display user controls on top
- * of the screen being captured.
- *
- * <p>Apps targeting SDK version {@link android.os.Build.VERSION_CODES#Q} or
- * later must set the
- * {@link android.R.attr#foregroundServiceType foregroundServiceType}
- * attribute to {@code mediaProjection} in the
- * <a href="/guide/topics/manifest/service-element">
- * <code><service></code></a> element of the app's manifest file;
- * {@code mediaProjection} is equivalent to
+ * capture request. The result code and data from the request are provided by overriding
+ * {@link Activity#onActivityResult(int, int, Intent) onActivityResult(int, int, Intent)},
+ * which is called after starting an activity using {@link #createScreenCaptureIntent()}.
+ * <p>
+ * Starting from Android {@link android.os.Build.VERSION_CODES#R R}, if your application
+ * requests the {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW SYSTEM_ALERT_WINDOW}
+ * permission, and the user has not explicitly denied it, the permission will be automatically
+ * granted until the projection is stopped. The permission allows your app to display user
+ * controls on top of the screen being captured.
+ * </p>
+ * <p>
+ * An app targeting SDK version {@link android.os.Build.VERSION_CODES#Q Q} or later must
+ * invoke {@code getMediaProjection} and maintain the capture session
+ * ({@link MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * android.hardware.display.VirtualDisplay.Callback, Handler)
+ * MediaProjection#createVirtualDisplay}) while running a foreground service. The app must set
+ * the {@link android.R.attr#foregroundServiceType foregroundServiceType} attribute to
* {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
- * FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ * FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION} in the
+ * <a href="/guide/topics/manifest/service-element"><code><service></code></a> element of
+ * the app's manifest file.
+ * </p>
*
- * @see <a href="/guide/components/foreground-services">
- * Foreground services developer guide</a>
- * @see <a href="/guide/topics/large-screens/media-projection">
- * Media projection developer guide</a>
- *
- * @param resultCode The result code from
- * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)
- * onActivityResult(int, int, Intent)}.
- * @param resultData The result data from
- * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)
- * onActivityResult(int, int, Intent)}.
- * @return The media projection obtained from a successful screen capture
- * request, or null if the result of the screen capture request is not
- * {@link Activity#RESULT_OK RESULT_OK}.
+ * @param resultCode The result code from {@link Activity#onActivityResult(int, int, Intent)
+ * onActivityResult(int, int, Intent)}.
+ * @param resultData The result data from {@link Activity#onActivityResult(int, int, Intent)
+ * onActivityResult(int, int, Intent)}.
+ * @return The media projection obtained from a successful screen capture request, or null if
+ * the result of the screen capture request is not {@link Activity#RESULT_OK RESULT_OK}.
* @throws IllegalStateException On
- * pre-{@link android.os.Build.VERSION_CODES#Q Q} devices if a
- * previously obtained {@code MediaProjection} from the same
- * {@code resultData} has not yet been stopped.
+ * pre-{@link android.os.Build.VERSION_CODES#Q Q} devices if a
+ * previously obtained {@code MediaProjection} from the same
+ * {@code resultData} has not yet been stopped.
+ * @throws SecurityException On {@link android.os.Build.VERSION_CODES#Q Q}+ devices if not
+ * invoked from a foreground service with type
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
+ * FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}, unless caller is a
+ * privileged app.
+ * @see <a href="/guide/components/foreground-services">
+ * Foreground services developer guide</a>
+ * @see <a href="/guide/topics/large-screens/media-projection">
+ * Media projection developer guide</a>
*/
public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
if (resultCode != Activity.RESULT_OK || resultData == null) {
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index de1b164..8459538 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -317,25 +317,21 @@
/**
* Time shift mode: off.
* <p>Time shift is disabled.
- * @hide
*/
public static final int TIME_SHIFT_MODE_OFF = 1;
/**
* Time shift mode: local.
* <p>Time shift is handle locally, using on-device data. E.g. playing a local file.
- * @hide
*/
public static final int TIME_SHIFT_MODE_LOCAL = 2;
/**
* Time shift mode: network.
* <p>Time shift is handle remotely via network. E.g. online streaming.
- * @hide
*/
public static final int TIME_SHIFT_MODE_NETWORK = 3;
/**
* Time shift mode: auto.
* <p>Time shift mode is handled automatically.
- * @hide
*/
public static final int TIME_SHIFT_MODE_AUTO = 4;
@@ -774,7 +770,11 @@
* Informs the app available speeds for time-shifting.
* @param session A {@link TvInputManager.Session} associated with this callback.
* @param speeds An ordered array of playback speeds, expressed as values relative to the
- * normal playback speed 1.0.
+ * normal playback speed (1.0), at which the current content can be played as
+ * a time-shifted broadcast. This is an empty array if the supported playback
+ * speeds are unknown or the video/broadcast is not in time shift mode. If
+ * currently in time shift mode, this array will normally include at least
+ * the values 1.0 (normal speed) and 0.0 (paused).
* @see PlaybackParams#getSpeed()
*/
public void onAvailableSpeeds(Session session, float[] speeds) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 000ed3b..4bc137d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1117,7 +1117,6 @@
* {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
* {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
- * @hide
*/
public void notifyTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1141,9 +1140,12 @@
* <p>This should be called when time-shifting is enabled.
*
* @param speeds An ordered array of playback speeds, expressed as values relative to the
- * normal playback speed 1.0.
+ * normal playback speed (1.0), at which the current content can be played as
+ * a time-shifted broadcast. This is an empty array if the supported playback
+ * speeds are unknown or the video/broadcast is not in time shift mode. If
+ * currently in time shift mode, this array will normally include at least
+ * the values 1.0 (normal speed) and 0.0 (paused).
* @see PlaybackParams#getSpeed()
- * @hide
*/
public void notifyAvailableSpeeds(@NonNull float[] speeds) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1191,7 +1193,6 @@
*
* @param available {@code true} if cueing message is available; {@code false} if it becomes
* unavailable.
- * @hide
*/
public void notifyCueingMessageAvailability(boolean available) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1575,7 +1576,6 @@
* {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
* {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
- * @hide
*/
public void onTimeShiftSetMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
}
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index cdeef2b..9a995a0 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -78,18 +78,28 @@
*
* @param view The related {@link TvInteractiveAppView} instance that is linked to this TV
* recording client. {@code null} to unlink the view.
- * @param recordingId The ID of the recording which is assigned by applications. {@code null} is
- * valid only when the TvInteractiveAppView parameter is null.
- * @hide
+ * @param recordingId The ID of the recording which is assigned by the TV application.
+ * {@code null} if and only if the TvInteractiveAppView parameter is
+ * {@code null}.
+ * @throws IllegalArgumentException when recording ID is {@code null} and the
+ * TvInteractiveAppView is not {@code null}; or when recording
+ * ID is not {@code null} and the TvInteractiveAppView is
+ * {@code null}.
+ * @see TvInteractiveAppView#notifyRecordingScheduled(String, String)
+ * @see TvInteractiveAppView#notifyRecordingStarted(String, String)
*/
public void setTvInteractiveAppView(
@Nullable TvInteractiveAppView view, @Nullable String recordingId) {
if (view != null && recordingId == null) {
throw new IllegalArgumentException(
- "null recordingId is allowed only when the view is null");
+ "null recordingId is not allowed only when the view is not null");
+ }
+ if (view == null && recordingId != null) {
+ throw new IllegalArgumentException(
+ "recordingId should be null when the view is null");
}
mTvIAppView = view;
- mRecordingId = view == null ? null : recordingId;
+ mRecordingId = recordingId;
}
/**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 372fa6d..3ef61f2 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -633,7 +633,6 @@
* {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
* {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
- * @hide
*/
public void timeShiftSetMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
if (mSession != null) {
@@ -1193,7 +1192,6 @@
* @param inputId The ID of the TV input bound to this view.
* @param available The current availability of cueing message. {@code true} if cueing
* message is available; {@code false} if it becomes unavailable.
- * @hide
*/
public void onCueingMessageAvailability(@NonNull String inputId, boolean available) {
}
@@ -1206,7 +1204,6 @@
* {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
* {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
- * @hide
*/
public void onTimeShiftMode(
@NonNull String inputId, @TvInputManager.TimeShiftMode int mode) {
@@ -1217,11 +1214,14 @@
*
* @param inputId The ID of the TV input bound to this view.
* @param speeds An ordered array of playback speeds, expressed as values relative to the
- * normal playback speed 1.0.
+ * normal playback speed (1.0), at which the current content can be played as
+ * a time-shifted broadcast. This is an empty array if the supported playback
+ * speeds are unknown or the video/broadcast is not in time shift mode. If
+ * currently in time shift mode, this array will normally include at least
+ * the values 1.0 (normal speed) and 0.0 (paused).
* @see PlaybackParams#getSpeed()
- * @hide
*/
- public void onAvailableSpeeds(@NonNull String inputId, float[] speeds) {
+ public void onAvailableSpeeds(@NonNull String inputId, @NonNull float[] speeds) {
}
/**
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index aac2d61..36954ad 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -51,12 +51,12 @@
void onRequestCurrentTvInputId(int seq);
void onRequestTimeShiftMode(int seq);
void onRequestAvailableSpeeds(int seq);
- void onRequestStartRecording(in Uri programUri, int seq);
+ void onRequestStartRecording(in String requestId, in Uri programUri, int seq);
void onRequestStopRecording(in String recordingId, int seq);
- void onRequestScheduleRecording(in String inputId, in Uri channelUri, in Uri programUri,
- in Bundle params, int seq);
- void onRequestScheduleRecording2(in String inputId, in Uri channelUri, long start,
- long duration, int repeat, in Bundle params, int seq);
+ void onRequestScheduleRecording(in String requestId, in String inputId, in Uri channelUri,
+ in Uri programUri, in Bundle params, int seq);
+ void onRequestScheduleRecording2(in String requestId, in String inputId, in Uri channelUri,
+ long start, long duration, int repeat, in Bundle params, int seq);
void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq);
void onRequestTvRecordingInfo(in String recordingId, int seq);
void onRequestTvRecordingInfoList(in int type, int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index e362af2..89847a7 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -91,7 +91,8 @@
void notifyContentAllowed(in IBinder sessionToken, int userId);
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
- void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
+ void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, String requestId,
+ int userId);
void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId);
void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 8d77141..f17d1b7 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -70,7 +70,7 @@
void notifyContentAllowed();
void notifyContentBlocked(in String rating);
void notifySignalStrength(int strength);
- void notifyRecordingStarted(in String recordingId);
+ void notifyRecordingStarted(in String recordingId, in String requestId);
void notifyRecordingStopped(in String recordingId);
void notifyTvMessage(in String type, in Bundle data);
void setSurface(in Surface surface);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index b71f23c..7db8604 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -50,12 +50,12 @@
void onRequestCurrentTvInputId();
void onRequestTimeShiftMode();
void onRequestAvailableSpeeds();
- void onRequestStartRecording(in Uri programUri);
+ void onRequestStartRecording(in String requestId, in Uri programUri);
void onRequestStopRecording(in String recordingId);
- void onRequestScheduleRecording(in String inputId, in Uri channelUri, in Uri programUri,
- in Bundle params);
- void onRequestScheduleRecording2(in String inputId, in Uri channelUri, long start,
- long duration, int repeat, in Bundle params);
+ void onRequestScheduleRecording(in String requestId, in String inputId, in Uri channelUri,
+ in Uri programUri, in Bundle params);
+ void onRequestScheduleRecording2(in String requestId, in String inputId, in Uri channelUri,
+ long start, long duration, int repeat, in Bundle params);
void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo);
void onRequestTvRecordingInfo(in String recordingId);
void onRequestTvRecordingInfoList(in int type);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index f009cea..ba30e79 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -206,7 +206,9 @@
break;
}
case DO_NOTIFY_RECORDING_STARTED: {
- mSessionImpl.notifyRecordingStarted((String) msg.obj);
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyRecordingStarted((String) args.arg1, (String) args.arg2);
+ args.recycle();
break;
}
case DO_NOTIFY_RECORDING_STOPPED: {
@@ -555,9 +557,9 @@
}
@Override
- public void notifyRecordingStarted(String recordingId) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(
- DO_NOTIFY_RECORDING_STARTED, recordingId));
+ public void notifyRecordingStarted(String recordingId, String requestId) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+ DO_NOTIFY_RECORDING_STARTED, recordingId, recordingId));
}
@Override
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index c88c8d4..3e31bce3 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -542,14 +542,14 @@
}
@Override
- public void onRequestStartRecording(Uri programUri, int seq) {
+ public void onRequestStartRecording(String requestId, Uri programUri, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
- record.postRequestStartRecording(programUri);
+ record.postRequestStartRecording(requestId, programUri);
}
}
@@ -566,21 +566,8 @@
}
@Override
- public void onRequestScheduleRecording(String inputId, Uri channelUri, Uri programUri,
- Bundle params, int seq) {
- synchronized (mSessionCallbackRecordMap) {
- SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
- if (record == null) {
- Log.e(TAG, "Callback not found for seq " + seq);
- return;
- }
- record.postRequestScheduleRecording(inputId, channelUri, programUri, params);
- }
- }
-
- @Override
- public void onRequestScheduleRecording2(String inputId, Uri channelUri, long startTime,
- long duration, int repeatDays, Bundle params, int seq) {
+ public void onRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
+ Uri programUri, Bundle params, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
@@ -588,7 +575,22 @@
return;
}
record.postRequestScheduleRecording(
- inputId, channelUri, startTime, duration, repeatDays, params);
+ requestId, inputId, channelUri, programUri, params);
+ }
+ }
+
+ @Override
+ public void onRequestScheduleRecording2(String requestId, String inputId,
+ Uri channelUri, long startTime, long duration, int repeatDays, Bundle params,
+ int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestScheduleRecording(requestId, inputId, channelUri, startTime,
+ duration, repeatDays, params);
}
}
@@ -935,7 +937,6 @@
* can be detected by the system.
*
* @return List of {@link AppLinkInfo} for each package that deslares its app link information.
- * @hide
*/
@NonNull
public List<AppLinkInfo> getAppLinkInfoList() {
@@ -1268,13 +1269,13 @@
}
}
- void notifyRecordingStarted(String recordingId) {
+ void notifyRecordingStarted(String recordingId, String requestId) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
- mService.notifyRecordingStarted(mToken, recordingId, mUserId);
+ mService.notifyRecordingStarted(mToken, recordingId, requestId, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2130,11 +2131,11 @@
});
}
- void postRequestStartRecording(Uri programUri) {
+ void postRequestStartRecording(String requestId, Uri programUri) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mSessionCallback.onRequestStartRecording(mSession, programUri);
+ mSessionCallback.onRequestStartRecording(mSession, requestId, programUri);
}
});
}
@@ -2148,24 +2149,24 @@
});
}
- void postRequestScheduleRecording(String inputId, Uri channelUri, Uri programUri,
- Bundle params) {
+ void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
+ Uri programUri, Bundle params) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onRequestScheduleRecording(
- mSession, inputId, channelUri, programUri, params);
+ mSession, requestId, inputId, channelUri, programUri, params);
}
});
}
- void postRequestScheduleRecording(String inputId, Uri channelUri, long startTime,
- long duration, int repeatDays, Bundle params) {
+ void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
+ long startTime, long duration, int repeatDays, Bundle params) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mSessionCallback.onRequestScheduleRecording(
- mSession, inputId, channelUri, startTime, duration, repeatDays, params);
+ mSessionCallback.onRequestScheduleRecording(mSession, requestId, inputId,
+ channelUri, startTime, duration, repeatDays, params);
}
});
}
@@ -2406,7 +2407,7 @@
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
* @param programUri The Uri of the program to be recorded.
*/
- public void onRequestStartRecording(Session session, Uri programUri) {
+ public void onRequestStartRecording(Session session, String requestId, Uri programUri) {
}
/**
@@ -2421,7 +2422,7 @@
/**
* This is called when
- * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, Uri, Bundle)}
+ * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, Uri, Bundle)}
* is called.
*
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
@@ -2434,13 +2435,14 @@
* @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
*/
- public void onRequestScheduleRecording(Session session, @NonNull String inputId,
- @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params) {
+ public void onRequestScheduleRecording(Session session, @NonNull String requestId,
+ @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri,
+ @NonNull Bundle params) {
}
/**
* This is called when
- * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, long, long, int, Bundle)}
+ * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, long, long, int, Bundle)}
* is called.
*
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
@@ -2455,9 +2457,9 @@
* @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
*/
- public void onRequestScheduleRecording(Session session, @NonNull String inputId,
- @NonNull Uri channelUri, long startTime, long duration, int repeatDays,
- @NonNull Bundle params) {
+ public void onRequestScheduleRecording(Session session, @NonNull String requestId,
+ @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration,
+ int repeatDays, @NonNull Bundle params) {
}
/**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 80b6e21..1ae82f4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -231,41 +231,34 @@
* Time shift command type: play.
*
* @see TvView#timeShiftPlay(String, Uri)
- * @hide
*/
public static final String TIME_SHIFT_COMMAND_TYPE_PLAY = "play";
/**
* Time shift command type: pause.
*
* @see TvView#timeShiftPause()
- * @hide
*/
public static final String TIME_SHIFT_COMMAND_TYPE_PAUSE = "pause";
/**
* Time shift command type: resume.
*
* @see TvView#timeShiftResume()
- * @hide
*/
public static final String TIME_SHIFT_COMMAND_TYPE_RESUME = "resume";
/**
* Time shift command type: seek to.
*
* @see TvView#timeShiftSeekTo(long)
- * @hide
*/
public static final String TIME_SHIFT_COMMAND_TYPE_SEEK_TO = "seek_to";
/**
* Time shift command type: set playback params.
*
* @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
- * @hide
*/
public static final String TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS = "set_playback_params";
/**
* Time shift command type: set time shift mode.
- *
- * @hide
*/
public static final String TIME_SHIFT_COMMAND_TYPE_SET_MODE = "set_mode";
@@ -274,7 +267,6 @@
* <p>Type: android.net.Uri
*
* @see #TIME_SHIFT_COMMAND_TYPE_PLAY
- * @hide
*/
public static final String COMMAND_PARAMETER_KEY_PROGRAM_URI = "command_program_uri";
/**
@@ -282,7 +274,6 @@
* <p>Type: long
*
* @see #TIME_SHIFT_COMMAND_TYPE_SEEK_TO
- * @hide
*/
public static final String COMMAND_PARAMETER_KEY_TIME_POSITION = "command_time_position";
/**
@@ -290,7 +281,6 @@
* <p>Type: android.media.PlaybackParams
*
* @see #TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS
- * @hide
*/
public static final String COMMAND_PARAMETER_KEY_PLAYBACK_PARAMS = "command_playback_params";
/**
@@ -301,7 +291,6 @@
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
*
* @see #TIME_SHIFT_COMMAND_TYPE_SET_MODE
- * @hide
*/
public static final String COMMAND_PARAMETER_KEY_TIME_SHIFT_MODE = "command_time_shift_mode";
@@ -589,18 +578,24 @@
/**
* Receives current time shift mode.
+ *
* @param mode The current time shift mode. The value is one of the following:
* {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
* {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
- * @hide
*/
public void onTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
}
/**
- * Receives available speeds.
- * @hide
+ * Receives available playback speeds.
+ *
+ * @param speeds An ordered array of playback speeds, expressed as values relative to the
+ * normal playback speed (1.0), at which the current content can be played as
+ * a time-shifted broadcast. This is an empty array if the supported playback
+ * speeds are unknown or the video/broadcast is not in time shift mode. If
+ * currently in time shift mode, this array will normally include at least
+ * the values 1.0 (normal speed) and 0.0 (paused).
*/
public void onAvailableSpeeds(@NonNull float[] speeds) {
}
@@ -624,21 +619,28 @@
public void onTvRecordingInfoList(@NonNull List<TvRecordingInfo> recordingInfoList) {}
/**
- * Receives started recording's ID.
+ * This is called when a recording has been started.
+ *
+ * <p>When a scheduled recording is started, this is also called, and the request ID in this
+ * case is {@code null}.
*
* @param recordingId The ID of the recording started. The TV app should provide and
* maintain this ID to identify the recording in the future.
+ * @param requestId The ID of the request when
+ * {@link #requestStartRecording(String, Uri)} is called.
+ * {@code null} if the recording is not triggered by a
+ * {@link #requestStartRecording(String, Uri)} request.
* @see #onRecordingStopped(String)
*/
- public void onRecordingStarted(@NonNull String recordingId) {
+ public void onRecordingStarted(@NonNull String recordingId, @Nullable String requestId) {
}
/**
- * Receives stopped recording's ID.
+ * This is called when the recording has been stopped.
*
* @param recordingId The ID of the recording stopped. This ID is created and maintained by
* the TV app when the recording was started.
- * @see #onRecordingStarted(String)
+ * @see #onRecordingStarted(String, String)
*/
public void onRecordingStopped(@NonNull String recordingId) {
}
@@ -648,10 +650,9 @@
* session for the corresponding TV input.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @param inputId The ID of the TV input bound to the current TvRecordingClient.
* @see android.media.tv.TvRecordingClient.RecordingCallback#onConnectionFailed(String)
- * @hide
*/
public void onRecordingConnectionFailed(
@NonNull String recordingId, @NonNull String inputId) {
@@ -661,10 +662,9 @@
* This is called when the connection to the current recording session is lost.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @param inputId The ID of the TV input bound to the current TvRecordingClient.
* @see android.media.tv.TvRecordingClient.RecordingCallback#onDisconnected(String)
- * @hide
*/
public void onRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) {
}
@@ -674,10 +674,9 @@
* ready to start recording.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @param channelUri The URI of the tuned channel.
* @see android.media.tv.TvRecordingClient.RecordingCallback#onTuned(Uri)
- * @hide
*/
public void onRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) {
}
@@ -687,7 +686,7 @@
* recording session is created until it is released.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @param err The error code. Should be one of the following.
* <ul>
* <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
@@ -695,7 +694,6 @@
* <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
* </ul>
* @see android.media.tv.TvRecordingClient.RecordingCallback#onError(int)
- * @hide
*/
public void onRecordingError(
@NonNull String recordingId, @TvInputManager.RecordingError int err) {
@@ -707,9 +705,9 @@
* @param recordingId The ID assigned to this recording by the app. It can be used to send
* recording related requests such as
* {@link #requestStopRecording(String)}.
- * @param requestId The ID of the request when requestScheduleRecording is called.
+ * @param requestId The ID of the request when
+ * {@link #requestScheduleRecording} is called.
* {@code null} if the recording is not triggered by a request.
- * @hide
*/
public void onRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) {
}
@@ -742,8 +740,8 @@
/**
* Called when the time shift {@link android.media.PlaybackParams} is set or changed.
*
+ * @param params The new {@link PlaybackParams} that was set or changed.
* @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
- * @hide
*/
public void onTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
}
@@ -753,17 +751,25 @@
*
* @see TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)
* @see android.media.tv.TvInputService.Session#notifyTimeShiftStatusChanged(int)
- * @hide
+ * @param inputId The ID of the input for which the time shift status has changed.
+ * @param status The status of which the input has changed to. Should be one of the
+ * following.
+ * <ul>
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
+ * </ul>
*/
public void onTimeShiftStatusChanged(
- @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
- }
+ @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {}
/**
* Called when time shift start position is changed.
*
* @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged(String, long)
- * @hide
+ * @param inputId The ID of the input for which the time shift start position has changed.
+ * @param timeMs The start position for time shifting, in milliseconds since the epoch.
*/
public void onTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
}
@@ -772,7 +778,8 @@
* Called when time shift current position is changed.
*
* @see TvView.TimeShiftPositionCallback#onTimeShiftCurrentPositionChanged(String, long)
- * @hide
+ * @param inputId The ID of the input for which the time shift current position has changed.
+ * @param timeMs The current position for time shifting, in milliseconds since the epoch.
*/
public void onTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
}
@@ -1086,7 +1093,6 @@
*
* @param cmdType type of the specific command
* @param parameters parameters of the specific command
- * @hide
*/
@CallSuper
public void sendTimeShiftCommandRequest(
@@ -1275,7 +1281,6 @@
/**
* Requests time shift mode.
- * @hide
*/
@CallSuper
public void requestTimeShiftMode() {
@@ -1299,7 +1304,6 @@
/**
* Requests available speeds for time shift.
- * @hide
*/
@CallSuper
public void requestAvailableSpeeds() {
@@ -1331,18 +1335,22 @@
* program, whereas null {@code programUri} does not impose such a requirement and the
* recording can span across multiple TV programs.
*
+ * @param requestId The ID of this request which is used to match the corresponding
+ * response. The request ID in
+ * {@link #onRecordingStarted(String, String)} for this request is the
+ * same as the ID sent here.
* @param programUri The URI for the TV program to record.
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
*/
@CallSuper
- public void requestStartRecording(@Nullable Uri programUri) {
+ public void requestStartRecording(@NonNull String requestId, @Nullable Uri programUri) {
executeOrPostRunnableOnMainThread(() -> {
try {
if (DEBUG) {
Log.d(TAG, "requestStartRecording");
}
if (mSessionCallback != null) {
- mSessionCallback.onRequestStartRecording(programUri);
+ mSessionCallback.onRequestStartRecording(requestId, programUri);
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestStartRecording", e);
@@ -1357,7 +1365,7 @@
* call {@link android.media.tv.TvRecordingClient#stopRecording()}.
*
* @param recordingId The ID of the recording to stop. This is provided by the TV app in
- * {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @see android.media.tv.TvRecordingClient#stopRecording()
*/
@CallSuper
@@ -1379,6 +1387,10 @@
/**
* Requests scheduling of a recording.
*
+ * @param requestId The ID of this request which is used to match the corresponding
+ * response. The request ID in
+ * {@link #onRecordingScheduled(String, String)} for this request is the
+ * same as the ID sent here.
* @param inputId The ID of the TV input for the given channel.
* @param channelUri The URI of a channel to be recorded.
* @param programUri The URI of the TV program to be recorded.
@@ -1387,11 +1399,10 @@
* will not create conflicting keys.
* @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
- * @hide
*/
@CallSuper
- public void requestScheduleRecording(@NonNull String inputId, @NonNull Uri channelUri,
- @NonNull Uri programUri, @NonNull Bundle params) {
+ public void requestScheduleRecording(@NonNull String requestId, @NonNull String inputId,
+ @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params) {
executeOrPostRunnableOnMainThread(() -> {
try {
if (DEBUG) {
@@ -1399,7 +1410,7 @@
}
if (mSessionCallback != null) {
mSessionCallback.onRequestScheduleRecording(
- inputId, channelUri, programUri, params);
+ requestId, inputId, channelUri, programUri, params);
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestScheduleRecording", e);
@@ -1410,6 +1421,10 @@
/**
* Requests scheduling of a recording.
*
+ * @param requestId The ID of this request which is used to match the corresponding
+ * response. The request ID in
+ * {@link #onRecordingScheduled(String, String)} for this request is the
+ * same as the ID sent here.
* @param inputId The ID of the TV input for the given channel.
* @param channelUri The URI of a channel to be recorded.
* @param startTime The start time of the recording in milliseconds since epoch.
@@ -1420,19 +1435,19 @@
* will not create conflicting keys.
* @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
- * @hide
*/
@CallSuper
- public void requestScheduleRecording(@NonNull String inputId, @NonNull Uri channelUri,
- long startTime, long duration, int repeatDays, @NonNull Bundle params) {
+ public void requestScheduleRecording(@NonNull String requestId, @NonNull String inputId,
+ @NonNull Uri channelUri, long startTime, long duration, int repeatDays,
+ @NonNull Bundle params) {
executeOrPostRunnableOnMainThread(() -> {
try {
if (DEBUG) {
Log.d(TAG, "requestScheduleRecording");
}
if (mSessionCallback != null) {
- mSessionCallback.onRequestScheduleRecording2(
- inputId, channelUri, startTime, duration, repeatDays, params);
+ mSessionCallback.onRequestScheduleRecording2(requestId, inputId, channelUri,
+ startTime, duration, repeatDays, params);
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestScheduleRecording", e);
@@ -1444,7 +1459,7 @@
* Sets the recording info for the specified recording
*
* @param recordingId The ID of the recording to set the info for. This is provided by the
- * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+ * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @param recordingInfo The {@link TvRecordingInfo} to set to the recording.
*/
@CallSuper
@@ -1467,7 +1482,8 @@
/**
* Gets the recording info for the specified recording
* @param recordingId The ID of the recording to set the info for. This is provided by the
- * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+ * TV app in
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
*/
@CallSuper
public void requestTvRecordingInfo(@NonNull String recordingId) {
@@ -1756,10 +1772,10 @@
}
/**
- * Calls {@link #onRecordingStarted(String)}.
+ * Calls {@link #onRecordingStarted(String, String)}.
*/
- void notifyRecordingStarted(String recordingId) {
- onRecordingStarted(recordingId);
+ void notifyRecordingStarted(String recordingId, String requestId) {
+ onRecordingStarted(recordingId, requestId);
}
/**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java
index acc2444..0455d7b 100644
--- a/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java
@@ -68,15 +68,9 @@
public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2;
/** Ginga interactive app type */
public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4;
- /**
- * Targeted Advertisement interactive app type
- * @hide
- */
+ /** Targeted Advertisement interactive app type */
public static final int INTERACTIVE_APP_TYPE_TARGETED_AD = 0x8;
- /**
- * Other interactive app type
- * @hide
- */
+ /** Other interactive app type */
public static final int INTERACTIVE_APP_TYPE_OTHER = 0x80000000;
private final ResolveInfo mService;
@@ -180,7 +174,20 @@
}
/**
- * Gets supported interactive app types
+ * Gets supported interactive app types.
+ *
+ * <p>The supported interactive app types is in a bit map format. For example:
+ * <pre><code>
+ * int types = tvInteractiveAppInfo.getSupportedTypes();
+ * if (types & TvInteractiveAppInfo.INTERACTIVE_APP_TYPE_HBBTV != 0) {
+ * // HbbTV type is supported. Do something...
+ * }
+ * if (types & TvInteractiveAppInfo.INTERACTIVE_APP_TYPE_ATSC == 0) {
+ * // ATSC type is not supported. Do something...
+ * }
+ * </code></pre>
+ *
+ * @return An int bit map representing supported types.
*/
@InteractiveAppType
@NonNull
@@ -189,13 +196,12 @@
}
/**
- * Gets extra supported interactive app types which are not listed.
+ * Gets custom supported interactive app types which are not listed.
*
* @see #getSupportedTypes()
- * @hide
*/
@NonNull
- public List<String> getExtraSupportedTypes() {
+ public List<String> getCustomSupportedTypes() {
return mExtraTypes;
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 2c40f39..0a8de12 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -598,13 +598,12 @@
}
/**
- * Sends current time shift mode to related TV interactive app.
+ * Sends the current time shift mode to the TV interactive app bound to this view
*
* @param mode The current time shift mode. The value is one of the following:
* {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
* {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
* {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
- * @hide
*/
public void sendTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
if (DEBUG) {
@@ -616,12 +615,15 @@
}
/**
- * Sends available supported speeds to related TV interactive app.
+ * Sends the available supported playback speeds to the TV interactive app bound to this view.
*
- * @param speeds An ordered array of playback speeds, expressed as values relative to the normal
- * playback speed 1.0.
+ * @param speeds An ordered array of playback speeds, expressed as values relative to the
+ * normal playback speed (1.0), at which the current content can be played as
+ * a time-shifted broadcast. This is an empty array if the supported playback
+ * speeds are unknown or the video/broadcast is not in time shift mode. If
+ * currently in time shift mode, this array will normally include at least
+ * the values 1.0 (normal speed) and 0.0 (paused).
* @see PlaybackParams#getSpeed()
- * @hide
*/
public void sendAvailableSpeeds(@NonNull float[] speeds) {
if (DEBUG) {
@@ -665,19 +667,22 @@
}
/**
- * Alerts the TV interactive app that a recording has been started.
+ * Alerts the related TV interactive app service that a recording has been started.
*
* @param recordingId The ID of the recording started. This ID is created and maintained by the
* TV app and is used to identify the recording in the future.
+ *
+ * @param requestId The ID of the request when
+ * {@link TvInteractiveAppService.Session#requestStartRecording(String, Uri)}
+ * is called. {@code null} if the recording is not triggered by a request.
* @see TvInteractiveAppView#notifyRecordingStopped(String)
*/
- public void notifyRecordingStarted(@NonNull String recordingId) {
- // TODO: add request ID to identify and map the corresponding request.
+ public void notifyRecordingStarted(@NonNull String recordingId, @Nullable String requestId) {
if (DEBUG) {
Log.d(TAG, "notifyRecordingStarted");
}
if (mSession != null) {
- mSession.notifyRecordingStarted(recordingId);
+ mSession.notifyRecordingStarted(recordingId, recordingId);
}
}
@@ -686,7 +691,7 @@
*
* @param recordingId The ID of the recording stopped. This ID is created and maintained
* by the TV app when a recording is started.
- * @see TvInteractiveAppView#notifyRecordingStarted(String)
+ * @see TvInteractiveAppView#notifyRecordingStarted(String, String)
*/
public void notifyRecordingStopped(@NonNull String recordingId) {
if (DEBUG) {
@@ -743,7 +748,7 @@
* {@link android.media.PlaybackParams} is set or changed.
*
* @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
- * @hide
+ * @param params The new {@link PlaybackParams} that was set or changed.
*/
public void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
if (DEBUG) {
@@ -760,7 +765,15 @@
*
* @see TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)
* @see android.media.tv.TvInputService.Session#notifyTimeShiftStatusChanged(int)
- * @hide
+ * @param inputId The ID of the input for which the time shift status has changed.
+ * @param status The status of which the input has changed to. Should be one of the
+ * following.
+ * <ul>
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
+ * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
+ * </ul>
*/
public void notifyTimeShiftStatusChanged(
@NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
@@ -778,7 +791,8 @@
* start position is changed.
*
* @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged(String, long)
- * @hide
+ * @param inputId The ID of the input for which the time shift start position has changed.
+ * @param timeMs The start position for time shifting, in milliseconds since the epoch.
*/
public void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
if (DEBUG) {
@@ -795,7 +809,8 @@
* current position is changed.
*
* @see TvView.TimeShiftPositionCallback#onTimeShiftCurrentPositionChanged(String, long)
- * @hide
+ * @param inputId The ID of the input for which the time shift current position has changed.
+ * @param timeMs The current position for time shifting, in milliseconds since the epoch.
*/
public void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
if (DEBUG) {
@@ -812,7 +827,7 @@
* while establishing a connection to the recording session for the corresponding TV input.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link #notifyRecordingStarted(String, String)}
* @param inputId The ID of the TV input bound to the current TvRecordingClient.
* @see android.media.tv.TvRecordingClient.RecordingCallback#onConnectionFailed(String)
* @hide
@@ -833,7 +848,7 @@
* the current recording session is lost.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link #notifyRecordingStarted(String, String)}
* @param inputId The ID of the TV input bound to the current TvRecordingClient.
* @see android.media.tv.TvRecordingClient.RecordingCallback#onDisconnected(String)
* @hide
@@ -854,7 +869,7 @@
* has been tuned to the given channel and is ready to start recording.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link #notifyRecordingStarted(String, String)}
* @param channelUri The URI of the tuned channel.
* @see android.media.tv.TvRecordingClient.RecordingCallback#onTuned(Uri)
* @hide
@@ -876,7 +891,7 @@
* it is released.
*
* @param recordingId The ID of the related recording which is sent via
- * {@link #notifyRecordingStarted(String)}
+ * {@link #notifyRecordingStarted(String, String)}
* @param err The error code. Should be one of the following.
* <ul>
* <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
@@ -904,9 +919,9 @@
* @param recordingId The ID assigned to this recording by the app. It can be used to send
* recording related requests such as
* {@link TvInteractiveAppService.Session#requestStopRecording(String)}.
- * @param requestId The ID of the request when requestScheduleRecording is called.
+ * @param requestId The ID of the request when
+ * {@link TvInteractiveAppService.Session#requestScheduleRecording} is called.
* {@code null} if the recording is not triggered by a request.
- * @hide
*/
public void notifyRecordingScheduled(
@NonNull String recordingId, @Nullable String requestId) {
@@ -1068,7 +1083,6 @@
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param cmdType type of the command
* @param parameters parameters of the command
- * @hide
*/
public void onTimeShiftCommandRequest(
@NonNull String iAppServiceId,
@@ -1186,7 +1200,6 @@
* called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
- * @hide
*/
public void onRequestTimeShiftMode(@NonNull String iAppServiceId) {
}
@@ -1196,7 +1209,6 @@
* called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
- * @hide
*/
public void onRequestAvailableSpeeds(@NonNull String iAppServiceId) {
}
@@ -1206,12 +1218,15 @@
* is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param requestId The ID of this request which is used to match the corresponding
+ * response. The request ID in
+ * {@link #notifyRecordingStarted(String, String)}
+ * for this request should be the same as the ID received here.
* @param programUri The URI of the program to record
*
*/
- public void onRequestStartRecording(
- @NonNull String iAppServiceId,
- @Nullable Uri programUri) {
+ public void onRequestStartRecording(@NonNull String iAppServiceId,
+ @NonNull String requestId, @Nullable Uri programUri) {
}
/**
@@ -1220,8 +1235,8 @@
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param recordingId The ID of the recording to stop. This is provided by the TV app in
- * {@link #notifyRecordingStarted(String)}
- * @see #notifyRecordingStarted(String)
+ * {@link #notifyRecordingStarted(String, String)}
+ * @see #notifyRecordingStarted(String, String)
* @see #notifyRecordingStopped(String)
*/
public void onRequestStopRecording(
@@ -1231,10 +1246,14 @@
/**
* This is called when
- * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, Uri, Bundle)}
+ * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, Uri, Bundle)}
* is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param requestId The ID of this request which is used to match the corresponding
+ * response. The request ID in
+ * {@link #notifyRecordingScheduled(String, String)} for this request
+ * should be the same as the ID received here.
* @param inputId The ID of the TV input for the given channel.
* @param channelUri The URI of a channel to be recorded.
* @param programUri The URI of the TV program to be recorded.
@@ -1243,19 +1262,22 @@
* will not create conflicting keys.
* @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
- * @hide
*/
public void onRequestScheduleRecording(@NonNull String iAppServiceId,
- @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri,
- @NonNull Bundle params) {
+ @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri,
+ @NonNull Uri programUri, @NonNull Bundle params) {
}
/**
* This is called when
- * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, long, long, int, Bundle)}
+ * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, long, long, int, Bundle)}
* is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param requestId The ID of this request which is used to match the corresponding
+ * response. The request ID in
+ * {@link #notifyRecordingScheduled(String, String)} for this request
+ * should be the same as the ID received here.
* @param inputId The ID of the TV input for the given channel.
* @param channelUri The URI of a channel to be recorded.
* @param startTime The start time of the recording in milliseconds since epoch.
@@ -1266,11 +1288,10 @@
* will not create conflicting keys.
* @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
* @see android.media.tv.TvRecordingClient#startRecording(Uri)
- * @hide
*/
public void onRequestScheduleRecording(@NonNull String iAppServiceId,
- @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration,
- int repeatDays, @NonNull Bundle params) {
+ @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri,
+ long startTime, long duration, int repeatDays, @NonNull Bundle params) {
}
/**
@@ -1295,7 +1316,7 @@
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param recordingId The ID of the recording to set the info for. This is provided by the
- * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+ * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
* @param recordingInfo The {@link TvRecordingInfo} to set to the recording.
*/
public void onSetTvRecordingInfo(
@@ -1311,7 +1332,8 @@
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param recordingId The ID of the recording to get the info for. This is provided by the
- * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+ * TV app in
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
*/
public void onRequestTvRecordingInfo(
@NonNull String iAppServiceId,
@@ -1715,7 +1737,7 @@
}
@Override
- public void onRequestStartRecording(Session session, Uri programUri) {
+ public void onRequestStartRecording(Session session, String requestId, Uri programUri) {
if (DEBUG) {
Log.d(TAG, "onRequestStartRecording");
}
@@ -1724,7 +1746,7 @@
return;
}
if (mCallback != null) {
- mCallback.onRequestStartRecording(mIAppServiceId, programUri);
+ mCallback.onRequestStartRecording(mIAppServiceId, requestId, programUri);
}
}
@@ -1758,8 +1780,9 @@
}
@Override
- public void onRequestScheduleRecording(Session session, @NonNull String inputId,
- @NonNull Uri channelUri, Uri progarmUri, @NonNull Bundle params) {
+ public void onRequestScheduleRecording(Session session, @NonNull String requestId,
+ @NonNull String inputId, @NonNull Uri channelUri, Uri programUri,
+ @NonNull Bundle params) {
if (DEBUG) {
Log.d(TAG, "onRequestScheduleRecording");
}
@@ -1768,8 +1791,24 @@
return;
}
if (mCallback != null) {
- mCallback.onRequestScheduleRecording(mIAppServiceId, inputId, channelUri,
- progarmUri, params);
+ mCallback.onRequestScheduleRecording(mIAppServiceId, requestId, inputId, channelUri,
+ programUri, params);
+ }
+ }
+
+ public void onRequestScheduleRecording(Session session, @NonNull String requestId,
+ @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration,
+ int repeatDays, @NonNull Bundle params) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestScheduleRecording");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestScheduleRecording - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onRequestScheduleRecording(mIAppServiceId, requestId, inputId, channelUri,
+ startTime, duration, repeatDays, params);
}
}
@@ -1803,22 +1842,6 @@
}
}
- public void onRequestScheduleRecording(Session session, @NonNull String inputId,
- @NonNull Uri channelUri, long startTime, long duration, int repeatDays,
- @NonNull Bundle params) {
- if (DEBUG) {
- Log.d(TAG, "onRequestScheduleRecording");
- }
- if (this != mSessionCallback) {
- Log.w(TAG, "onRequestScheduleRecording - session not created");
- return;
- }
- if (mCallback != null) {
- mCallback.onRequestScheduleRecording(mIAppServiceId, inputId, channelUri, startTime,
- duration, repeatDays, params);
- }
- }
-
@Override
public void onRequestSigning(
Session session, String id, String algorithm, String alias, byte[] data) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 7d08b81..5a56945 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -805,6 +805,7 @@
acquireTRMSLock("close()");
try {
releaseAll();
+ mTunerResourceManager.unregisterClientProfile(mClientId);
TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
} finally {
releaseTRMSLock();
@@ -968,7 +969,6 @@
releaseDescramblers();
releaseFilters();
releaseDemux();
- mTunerResourceManager.unregisterClientProfile(mClientId);
}
/**
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index f258c27..d74f9b7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -275,6 +275,7 @@
ASurfaceTransaction_setGeometry; # introduced=29
ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
+ ASurfaceTransaction_setExtendedRangeBrightness; # introduced=UpsideDownCake
ASurfaceTransaction_setOnComplete; # introduced=29
ASurfaceTransaction_setOnCommit; # introduced=31
ASurfaceTransaction_setPosition; # introduced=31
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index b7f3596..904fa74 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -621,6 +621,30 @@
transaction->setHdrMetadata(surfaceControl, hdrMetadata);
}
+void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ float currentBufferRatio, float desiredRatio) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ if (!isfinite(currentBufferRatio) || currentBufferRatio < 1.0f) {
+ ALOGE("Ignore setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= 1.0f",
+ currentBufferRatio);
+ return;
+ }
+
+ if (!isfinite(desiredRatio) || desiredRatio < 1.0f) {
+ ALOGE("Ignore setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f",
+ desiredRatio);
+ return;
+ }
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setExtendedRangeBrightness(surfaceControl, currentBufferRatio, desiredRatio);
+}
+
void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl,
float r, float g, float b, float alpha,
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index cd6ed23..e18b4a9 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -25,10 +25,14 @@
#include <hwui/ImageDecoder.h>
#include <log/log.h>
#include <SkAndroidCodec.h>
+#include <SkAlphaType.h>
#include <SkCodec.h>
+#include <SkCodecAnimation.h>
#include <SkColorSpace.h>
+#include <SkColorType.h>
#include <SkImageInfo.h>
#include <SkRect.h>
+#include <SkRefCnt.h>
#include <SkSize.h>
#include <SkStream.h>
#include <utils/Color.h>
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
new file mode 100644
index 0000000..97d201d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_neutral1_200">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6.85,15Q7.625,15 8.238,14.55Q8.85,14.1 9.1,13.375L9.475,12.225Q9.875,11.025 9.275,10.012Q8.675,9 7.55,9H4.025L4.5,12.925Q4.625,13.8 5.287,14.4Q5.95,15 6.85,15ZM17.15,15Q18.05,15 18.712,14.4Q19.375,13.8 19.5,12.925L19.975,9H16.475Q15.35,9 14.75,10.025Q14.15,11.05 14.55,12.25L14.9,13.375Q15.15,14.1 15.762,14.55Q16.375,15 17.15,15ZM6.85,17Q5.2,17 3.963,15.912Q2.725,14.825 2.525,13.175L2,9H1V7H7.55Q8.65,7 9.562,7.537Q10.475,8.075 11,9H13.025Q13.55,8.075 14.463,7.537Q15.375,7 16.475,7H23V9H22L21.475,13.175Q21.275,14.825 20.038,15.912Q18.8,17 17.15,17Q15.725,17 14.588,16.188Q13.45,15.375 13,14.025L12.625,12.9Q12.575,12.725 12.525,12.537Q12.475,12.35 12.425,12H11.575Q11.525,12.3 11.475,12.487Q11.425,12.675 11.375,12.85L11,14Q10.55,15.35 9.413,16.175Q8.275,17 6.85,17Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_device_streaming.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_device_streaming.xml
new file mode 100644
index 0000000..af4fe4d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_device_streaming.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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,23V21H17Q17,21 17,21Q17,21 17,21V6H7V12H5V3Q5,2.175 5.588,1.587Q6.175,1 7,1H17Q17.825,1 18.413,1.587Q19,2.175 19,3V21Q19,21.825 18.413,22.413Q17.825,23 17,23ZM5,23V21Q5.825,21 6.412,21.587Q7,22.175 7,23ZM9,23Q9,21.35 7.825,20.175Q6.65,19 5,19V17Q7.5,17 9.25,18.75Q11,20.5 11,23ZM13,23Q13,19.65 10.675,17.325Q8.35,15 5,15V13Q7.075,13 8.9,13.787Q10.725,14.575 12.075,15.925Q13.425,17.275 14.213,19.1Q15,20.925 15,23ZM7,4H17V3Q17,3 17,3Q17,3 17,3H7Q7,3 7,3Q7,3 7,3ZM7,4V3Q7,3 7,3Q7,3 7,3Q7,3 7,3Q7,3 7,3V4Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
index 5f8d566..9065520 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
@@ -16,23 +16,12 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="24dp"
- android:width="24dp"
- android:viewportHeight="160"
- android:viewportWidth="160" >
- <path android:fillAlpha="0" android:fillColor="#000000"
- android:pathData="M69.48,83.33A26.97,24.46 0,0 1,42.92 107.8,26.97 24.46,0 0,1 15.56,84.07 26.97,24.46 0,0 1,41.29 58.9,26.97 24.46,0 0,1 69.43,81.86"
- android:strokeColor="#000000" android:strokeWidth="2.265"/>
- <path android:fillAlpha="0" android:fillColor="#000000"
- android:pathData="m143.73,83.58a26.97,24.46 0,0 1,-26.56 24.46,26.97 24.46,0 0,1 -27.36,-23.72 26.97,24.46 0,0 1,25.73 -25.18,26.97 24.46,0 0,1 28.14,22.96"
- android:strokeColor="#000000" android:strokeWidth="2.265"/>
- <path android:fillAlpha="0" android:fillColor="#000000"
- android:pathData="m69.42,82.98c20.37,-0.25 20.37,-0.25 20.37,-0.25"
- android:strokeColor="#000000" android:strokeWidth="2.265"/>
- <path android:fillAlpha="0" android:fillColor="#000000"
- android:pathData="M15.37,83.78 L1.9,56.83"
- android:strokeColor="#000000" android:strokeWidth="2.265"/>
- <path android:fillAlpha="0" android:fillColor="#000000"
- android:pathData="M143.67,82.75C154.48,57.9 154.48,58.04 154.48,58.04"
- android:strokeColor="#000000" android:strokeWidth="2.265"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6.85,15Q7.625,15 8.238,14.55Q8.85,14.1 9.1,13.375L9.475,12.225Q9.875,11.025 9.275,10.012Q8.675,9 7.55,9H4.025L4.5,12.925Q4.625,13.8 5.287,14.4Q5.95,15 6.85,15ZM17.15,15Q18.05,15 18.712,14.4Q19.375,13.8 19.5,12.925L19.975,9H16.475Q15.35,9 14.75,10.025Q14.15,11.05 14.55,12.25L14.9,13.375Q15.15,14.1 15.762,14.55Q16.375,15 17.15,15ZM6.85,17Q5.2,17 3.963,15.912Q2.725,14.825 2.525,13.175L2,9H1V7H7.55Q8.65,7 9.562,7.537Q10.475,8.075 11,9H13.025Q13.55,8.075 14.463,7.537Q15.375,7 16.475,7H23V9H22L21.475,13.175Q21.275,14.825 20.038,15.912Q18.8,17 17.15,17Q15.725,17 14.588,16.188Q13.45,15.375 13,14.025L12.625,12.9Q12.575,12.725 12.525,12.537Q12.475,12.35 12.425,12H11.575Q11.525,12.3 11.475,12.487Q11.425,12.675 11.375,12.85L11,14Q10.55,15.35 9.413,16.175Q8.275,17 6.85,17Z"/>
</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml
index 7295e78..d890afd 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml
@@ -22,7 +22,6 @@
android:viewportHeight="24"
android:tint="@android:color/system_accent1_600">
<path
- android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z"
- android:fillColor="#3C4043"
- android:fillType="evenOdd"/>
+ android:fillColor="@android:color/white"
+ android:pathData="M16,23V21H17Q17,21 17,21Q17,21 17,21V6H7V12H5V3Q5,2.175 5.588,1.587Q6.175,1 7,1H17Q17.825,1 18.413,1.587Q19,2.175 19,3V21Q19,21.825 18.413,22.413Q17.825,23 17,23ZM5,23V21Q5.825,21 6.412,21.587Q7,22.175 7,23ZM9,23Q9,21.35 7.825,20.175Q6.65,19 5,19V17Q7.5,17 9.25,18.75Q11,20.5 11,23ZM13,23Q13,19.65 10.675,17.325Q8.35,15 5,15V13Q7.075,13 8.9,13.787Q10.725,14.575 12.075,15.925Q13.425,17.275 14.213,19.1Q15,20.925 15,23ZM7,4H17V3Q17,3 17,3Q17,3 17,3H7Q7,3 7,3Q7,3 7,3ZM7,4V3Q7,3 7,3Q7,3 7,3Q7,3 7,3Q7,3 7,3V4Z"/>
</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index bf69ef4..f64a432 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -26,7 +26,6 @@
import androidx.activity.ComponentActivity
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
@@ -35,6 +34,7 @@
import com.android.credentialmanager.common.Constants
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
+import com.android.credentialmanager.common.StartBalIntentSenderForResultContract
import com.android.credentialmanager.createflow.CreateCredentialScreen
import com.android.credentialmanager.getflow.GetCredentialScreen
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
@@ -84,7 +84,7 @@
CredentialSelectorViewModel(credManRepo, userConfigRepo)
}
val launcher = rememberLauncherForActivityResult(
- ActivityResultContracts.StartIntentSenderForResult()
+ StartBalIntentSenderForResultContract()
) {
viewModel.onProviderActivityResult(ProviderActivityResult(it.resultCode, it.data))
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/StartBalIntentSenderForResultContract.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/StartBalIntentSenderForResultContract.kt
new file mode 100644
index 0000000..9952815
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/StartBalIntentSenderForResultContract.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common
+
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts
+
+/**
+ * A custom StartIntentSenderForResult contract implementation that attaches an [ActivityOptions]
+ * that opts in for background activity launch.
+ */
+class StartBalIntentSenderForResultContract :
+ ActivityResultContract<IntentSenderRequest, ActivityResult>() {
+ override fun createIntent(context: Context, input: IntentSenderRequest): Intent {
+ val activityOptionBundle =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ ).toBundle()
+ return Intent(
+ ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST
+ ).putExtra(
+ ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE,
+ activityOptionBundle
+ ).putExtra(
+ ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST,
+ input
+ )
+ }
+
+ override fun parseResult(
+ resultCode: Int,
+ intent: Intent?
+ ): ActivityResult = ActivityResult(resultCode, intent)
+}
\ No newline at end of file
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 4344e94..95b49a8 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -20,6 +20,9 @@
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.BackupTransport;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
@@ -27,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
@@ -100,6 +104,7 @@
private FileInputStream mCurFullRestoreStream;
private byte[] mFullRestoreBuffer;
private final LocalTransportParameters mParameters;
+ private final BackupManagerMonitor mMonitor = new TestBackupManagerMonitor();
private void makeDataDirs() {
mDataDir = mContext.getFilesDir();
@@ -887,4 +892,41 @@
public long getBackupQuota(String packageName, boolean isFullBackup) {
return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : KEY_VALUE_BACKUP_SIZE_QUOTA;
}
+
+ @Override
+ public BackupManagerMonitor getBackupManagerMonitor() {
+ return mMonitor;
+ }
+
+ private class TestBackupManagerMonitor extends BackupManagerMonitor {
+ @Override
+ public void onEvent(Bundle event) {
+ if (event == null || !mParameters.logAgentResults()) {
+ return;
+ }
+
+ if (event.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID)
+ == BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS) {
+ Log.i(TAG, "agent_logging_results {");
+ ArrayList<DataTypeResult> results = event.getParcelableArrayList(
+ BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+ DataTypeResult.class);
+ for (DataTypeResult result : results) {
+ Log.i(TAG, "\tdataType: " + result.getDataType());
+ Log.i(TAG, "\tsuccessCount: " + result.getSuccessCount());
+ Log.i(TAG, "\tfailCount: " + result.getFailCount());
+
+ if (!result.getErrors().isEmpty()) {
+ Log.i(TAG, "\terrors {");
+ for (String error : result.getErrors().keySet()) {
+ Log.i(TAG, "\t\t" + error + ": " + result.getErrors().get(error));
+ }
+ Log.i(TAG, "\t}");
+ }
+
+ Log.i(TAG, "}");
+ }
+ }
+ }
+ }
}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index 1ba1bc6..aaa18bf 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -29,11 +29,13 @@
private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only";
private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer";
private static final String KEY_IS_ENCRYPTED = "is_encrypted";
+ private static final String KEY_LOG_AGENT_RESULTS = "log_agent_results";
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
private boolean mIsDeviceTransfer;
private boolean mIsEncrypted;
+ private boolean mLogAgentResults;
public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
@@ -55,6 +57,10 @@
return mIsEncrypted;
}
+ boolean logAgentResults() {
+ return mLogAgentResults;
+ }
+
public String getSettingValue(ContentResolver resolver) {
return Settings.Secure.getString(resolver, SETTING);
}
@@ -64,5 +70,6 @@
mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false);
mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false);
+ mLogAgentResults = parser.getBoolean(KEY_LOG_AGENT_RESULTS, /* def */ false);
}
}
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 4b4cfb7..0cd9fe3 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -30,6 +30,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index af7b8b4..0ba47a8 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -23,6 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 50f8e54..6330848f 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -28,6 +28,6 @@
"com.android.adservices",
"com.android.cellbroadcast",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index bcedf50..8b976bb 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -23,6 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index 3ec4366a..13fcf8c 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -22,6 +22,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 372a276..825e6ac 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -25,6 +25,6 @@
"//apex_available:platform",
"com.android.adservices",
"com.android.cellbroadcast",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index 939977f..09691f1 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -23,7 +23,7 @@
"com.android.cellbroadcast",
"com.android.permission",
"com.android.adservices",
- "com.android.healthconnect",
+ "com.android.healthfitness",
"com.android.mediaprovider",
],
}
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index be77845..7f9014c 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -23,6 +23,6 @@
"com.android.adservices",
"com.android.cellbroadcast",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 9117524a..42af999 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -19,7 +19,7 @@
BUILD_TOOLS_VERSION = "30.0.3"
MIN_SDK = 21
TARGET_SDK = 33
- jetpack_compose_version = '1.4.0-alpha05'
+ jetpack_compose_version = '1.4.0-beta01'
jetpack_compose_compiler_version = '1.4.0'
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
index 08e6452..5f251b1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
@@ -64,7 +64,7 @@
return SettingsEntryBuilder.createInject(
owner = createSettingsPage(arguments),
- displayName = "ItemList_$opParam",
+ label = "ItemList_$opParam",
).setUiLayoutFn {
Preference(
object : PreferenceModel {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
index 8179356..98b27b7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
@@ -99,7 +99,7 @@
return SettingsEntryBuilder.createInject(
owner = createSettingsPage(arguments),
- displayName = "ItemOp_$opParam",
+ label = "ItemOp_$opParam",
).setUiLayoutFn {
// Item name is a runtime parameter, which needs to be read inside UiLayoutFn
val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index eca47b6..f01ff38 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -87,7 +87,7 @@
return SettingsEntryBuilder.createInject(
owner = createSettingsPage(arguments),
- displayName = "${name}_$stringParam",
+ label = "${name}_$stringParam",
)
.setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
.setUiLayoutFn {
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
index 6bf20be..f513830 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
index fa01348..73f2407 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
index 7e57958..6e860d3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 6f1b41c..640aa01 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -69,18 +69,16 @@
}
dependencies {
- String jetpack_lifecycle_version = "2.6.0-alpha03"
-
- api "androidx.appcompat:appcompat:1.7.0-alpha01"
+ api "androidx.appcompat:appcompat:1.7.0-alpha02"
api "androidx.slice:slice-builders:1.1.0-alpha02"
api "androidx.slice:slice-core:1.1.0-alpha02"
api "androidx.slice:slice-view:1.1.0-alpha02"
- api "androidx.compose.material3:material3:1.1.0-alpha05"
+ api "androidx.compose.material3:material3:1.1.0-alpha06"
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
- api "androidx.compose.runtime:runtime-livedata:1.4.0-alpha04"
+ api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx:$jetpack_lifecycle_version"
- api "androidx.lifecycle:lifecycle-runtime-compose:$jetpack_lifecycle_version"
+ api "androidx.lifecycle:lifecycle-livedata-ktx"
+ api "androidx.lifecycle:lifecycle-runtime-compose"
api "androidx.navigation:navigation-compose:2.6.0-alpha04"
api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
api "com.google.android.material:material:1.7.0-alpha03"
@@ -108,7 +106,6 @@
// Excludes files forked from Accompanist.
"com/android/settingslib/spa/framework/compose/DrawablePainter*",
- "com/android/settingslib/spa/framework/compose/Pager*",
// Excludes inline functions, which is not covered in Jacoco reports.
"com/android/settingslib/spa/framework/util/Collections*",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index d95ed05..444a3f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -50,22 +50,22 @@
}
fun SettingsEntry.debugBrief(): String {
- return "${owner.displayName}:$displayName"
+ return "${owner.displayName}:$label"
}
fun SettingsEntry.debugContent(entryRepository: SettingsEntryRepository): String {
val searchData = getSearchData()
val statusData = getStatusData()
- val entryPathWithName = entryRepository.getEntryPathWithDisplayName(id)
+ val entryPathWithLabel = entryRepository.getEntryPathWithLabel(id)
val entryPathWithTitle = entryRepository.getEntryPathWithTitle(id,
- searchData?.title ?: displayName)
+ searchData?.title ?: label)
val content = listOf(
"------ STATIC ------",
"id = $id",
"owner = ${owner.debugBrief()} ${owner.debugArguments()}",
"linkFrom = ${fromPage?.debugBrief()} ${fromPage?.debugArguments()}",
"linkTo = ${toPage?.debugBrief()} ${toPage?.debugArguments()}",
- "hierarchy_path = $entryPathWithName",
+ "hierarchy_path = $entryPathWithLabel",
"------ ATTRIBUTION ------",
"allowSearch = $isAllowSearch",
"isSearchDynamic = $isSearchDataDynamic",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 494e3cc..1fcc888 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -173,12 +173,12 @@
val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
- .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
+ .add(ColumnEnum.ENTRY_LABEL.id, entry.label)
.add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
.add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
.add(
ColumnEnum.ENTRY_HIERARCHY_PATH.id,
- entryRepository.getEntryPathWithDisplayName(entry.id)
+ entryRepository.getEntryPathWithLabel(entry.id)
)
}
return cursor
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
index fc6160e..9b46ec2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -33,7 +33,7 @@
// Columns related to entry
ENTRY_ID("entryId"),
- ENTRY_NAME("entryName"),
+ ENTRY_LABEL("entryLabel"),
ENTRY_ROUTE("entryRoute"),
ENTRY_INTENT_URI("entryIntent"),
ENTRY_HIERARCHY_PATH("entryPath"),
@@ -76,7 +76,7 @@
"entry_info", 200,
listOf(
ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_NAME,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.ENTRY_ROUTE,
ColumnEnum.ENTRY_INTENT_URI,
ColumnEnum.ENTRY_HIERARCHY_PATH,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index b92729d..90581b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -51,11 +51,13 @@
// The unique id of this entry, which is computed by name + owner + fromPage + toPage.
val id: String,
- // The name of the page, which is used to compute the unique id, and need to be stable.
+ // The name of the entry, which is used to compute the unique id, and need to be stable.
private val name: String,
- // The display name of the page, for better readability.
- val displayName: String,
+ // The label of the entry, for better readability.
+ // For migration mapping, this should match the android:key field in the old architecture
+ // if applicable.
+ val label: String,
// The owner page of this entry.
val owner: SettingsPage,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
index 67f9ea5..97d8de3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
@@ -21,14 +21,14 @@
import androidx.compose.runtime.remember
import com.android.settingslib.spa.framework.util.genEntryId
-private const val INJECT_ENTRY_NAME = "INJECT"
-private const val ROOT_ENTRY_NAME = "ROOT"
+private const val INJECT_ENTRY_LABEL = "INJECT"
+private const val ROOT_ENTRY_LABEL = "ROOT"
/**
* The helper to build a Settings Entry instance.
*/
class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
- private var displayName = name
+ private var label = name
private var fromPage: SettingsPage? = null
private var toPage: SettingsPage? = null
@@ -51,7 +51,7 @@
id = genEntryId(name, owner, fromPage, toPage),
name = name,
owner = owner,
- displayName = displayName,
+ label = label,
// linking data
fromPage = fromPage,
@@ -72,8 +72,8 @@
)
}
- fun setDisplayName(displayName: String): SettingsEntryBuilder {
- this.displayName = displayName
+ fun setLabel(label: String): SettingsEntryBuilder {
+ this.label = label
return this
}
@@ -147,19 +147,19 @@
return create(entryName, owner).setLink(toPage = owner)
}
- fun create(owner: SettingsPage, entryName: String, displayName: String? = null):
+ fun create(owner: SettingsPage, entryName: String, label: String? = null):
SettingsEntryBuilder {
- return SettingsEntryBuilder(entryName, owner).setDisplayName(displayName ?: entryName)
+ return SettingsEntryBuilder(entryName, owner).setLabel(label ?: entryName)
}
- fun createInject(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
- val name = displayName ?: "${INJECT_ENTRY_NAME}_${owner.displayName}"
- return createLinkTo(INJECT_ENTRY_NAME, owner).setDisplayName(name)
+ fun createInject(owner: SettingsPage, label: String? = null): SettingsEntryBuilder {
+ val label = label ?: "${INJECT_ENTRY_LABEL}_${owner.displayName}"
+ return createLinkTo(INJECT_ENTRY_LABEL, owner).setLabel(label)
}
- fun createRoot(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
- val name = displayName ?: "${ROOT_ENTRY_NAME}_${owner.displayName}"
- return createLinkTo(ROOT_ENTRY_NAME, owner).setDisplayName(name)
+ fun createRoot(owner: SettingsPage, label: String? = null): SettingsEntryBuilder {
+ val label = label ?: "${ROOT_ENTRY_LABEL}_${owner.displayName}"
+ return createLinkTo(ROOT_ENTRY_LABEL, owner).setLabel(label)
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 429f97b..8811b94 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -111,9 +111,9 @@
return entryPath
}
- fun getEntryPathWithDisplayName(entryId: String): List<String> {
+ fun getEntryPathWithLabel(entryId: String): List<String> {
val entryPath = getEntryPath(entryId)
- return entryPath.map { it.displayName }
+ return entryPath.map { it.label }
}
fun getEntryPathWithTitle(entryId: String, defaultTitle: String): List<String> {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
deleted file mode 100644
index 392089a..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.drop
-import kotlinx.coroutines.flow.filter
-
-/**
- * *************************************************************************************************
- * This file was forked from
- * https://github.com/google/accompanist/blob/main/pager/src/main/java/com/google/accompanist/pager/Pager.kt
- * and will be removed once it lands in AndroidX.
- */
-
-/**
- * A horizontally scrolling layout that allows users to flip between items to the left and right.
- *
- * @sample com.google.accompanist.sample.pager.HorizontalPagerSample
- *
- * @param count the number of pages.
- * @param modifier the modifier to apply to this layout.
- * @param state the state object to be used to control or observe the pager's state.
- * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
- * composed from the end to the start and [PagerState.currentPage] == 0 will mean
- * the first item is located at the end.
- * @param itemSpacing horizontal spacing to add between items.
- * @param key the scroll position will be maintained based on the key, which means if you
- * add/remove items before the current visible item the item with the given key will be kept as the
- * first visible one.
- * @param content a block which describes the content. Inside this block you can reference
- * [PagerScope.currentPage] and other properties in [PagerScope].
- */
-@Composable
-fun HorizontalPager(
- count: Int,
- modifier: Modifier = Modifier,
- state: PagerState = rememberPagerState(),
- reverseLayout: Boolean = false,
- itemSpacing: Dp = 0.dp,
- contentPadding: PaddingValues = PaddingValues(0.dp),
- verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
- key: ((page: Int) -> Any)? = null,
- content: @Composable PagerScope.(page: Int) -> Unit,
-) {
- Pager(
- count = count,
- state = state,
- modifier = modifier,
- isVertical = false,
- reverseLayout = reverseLayout,
- itemSpacing = itemSpacing,
- verticalAlignment = verticalAlignment,
- key = key,
- contentPadding = contentPadding,
- content = content
- )
-}
-
-/**
- * A vertically scrolling layout that allows users to flip between items to the top and bottom.
- *
- * @sample com.google.accompanist.sample.pager.VerticalPagerSample
- *
- * @param count the number of pages.
- * @param modifier the modifier to apply to this layout.
- * @param state the state object to be used to control or observe the pager's state.
- * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
- * composed from the bottom to the top and [PagerState.currentPage] == 0 will mean
- * the first item is located at the bottom.
- * @param itemSpacing vertical spacing to add between items.
- * @param key the scroll position will be maintained based on the key, which means if you
- * add/remove items before the current visible item the item with the given key will be kept as the
- * first visible one.
- * @param content a block which describes the content. Inside this block you can reference
- * [PagerScope.currentPage] and other properties in [PagerScope].
- */
-@Composable
-fun VerticalPager(
- count: Int,
- modifier: Modifier = Modifier,
- state: PagerState = rememberPagerState(),
- reverseLayout: Boolean = false,
- itemSpacing: Dp = 0.dp,
- contentPadding: PaddingValues = PaddingValues(0.dp),
- horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
- key: ((page: Int) -> Any)? = null,
- content: @Composable PagerScope.(page: Int) -> Unit,
-) {
- Pager(
- count = count,
- state = state,
- modifier = modifier,
- isVertical = true,
- reverseLayout = reverseLayout,
- itemSpacing = itemSpacing,
- horizontalAlignment = horizontalAlignment,
- key = key,
- contentPadding = contentPadding,
- content = content
- )
-}
-
-@Composable
-internal fun Pager(
- count: Int,
- modifier: Modifier,
- state: PagerState,
- reverseLayout: Boolean,
- itemSpacing: Dp,
- isVertical: Boolean,
- key: ((page: Int) -> Any)?,
- contentPadding: PaddingValues,
- verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
- horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
- content: @Composable PagerScope.(page: Int) -> Unit,
-) {
- require(count >= 0) { "pageCount must be >= 0" }
-
- LaunchedEffect(count) {
- state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
- }
-
- // Once a fling (scroll) has finished, notify the state
- LaunchedEffect(state) {
- // When a 'scroll' has finished, notify the state
- snapshotFlow { state.isScrollInProgress }
- .filter { !it }
- // initially isScrollInProgress is false as well and we want to start receiving
- // the events only after the real scroll happens.
- .drop(1)
- .collect { state.onScrollFinished() }
- }
- LaunchedEffect(state) {
- snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
- .distinctUntilChanged()
- .collect { state.updateCurrentPageBasedOnLazyListState() }
- }
- val density = LocalDensity.current
- LaunchedEffect(density, state, itemSpacing) {
- with(density) { state.itemSpacing = itemSpacing.roundToPx() }
- }
-
- val pagerScope = remember(state) { PagerScopeImpl(state) }
-
- // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate
- // as normal
- val consumeFlingNestedScrollConnection = remember(isVertical) {
- ConsumeFlingNestedScrollConnection(
- consumeHorizontal = !isVertical,
- consumeVertical = isVertical,
- pagerState = state,
- )
- }
-
- if (isVertical) {
- LazyColumn(
- state = state.lazyListState,
- verticalArrangement = Arrangement.spacedBy(itemSpacing, verticalAlignment),
- horizontalAlignment = horizontalAlignment,
- reverseLayout = reverseLayout,
- contentPadding = contentPadding,
- userScrollEnabled = false,
- modifier = modifier,
- ) {
- items(
- count = count,
- key = key,
- ) { page ->
- Box(
- Modifier
- // We don't any nested flings to continue in the pager, so we add a
- // connection which consumes them.
- // See: https://github.com/google/accompanist/issues/347
- .nestedScroll(connection = consumeFlingNestedScrollConnection)
- // Constraint the content height to be <= than the height of the pager.
- .fillParentMaxHeight()
- .wrapContentSize()
- ) {
- pagerScope.content(page)
- }
- }
- }
- } else {
- LazyRow(
- state = state.lazyListState,
- verticalAlignment = verticalAlignment,
- horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment),
- reverseLayout = reverseLayout,
- contentPadding = contentPadding,
- userScrollEnabled = false,
- modifier = modifier,
- ) {
- items(
- count = count,
- key = key,
- ) { page ->
- Box(
- Modifier
- // We don't any nested flings to continue in the pager, so we add a
- // connection which consumes them.
- // See: https://github.com/google/accompanist/issues/347
- .nestedScroll(connection = consumeFlingNestedScrollConnection)
- // Constraint the content width to be <= than the width of the pager.
- .fillParentMaxWidth()
- .wrapContentSize()
- ) {
- pagerScope.content(page)
- }
- }
- }
- }
-}
-
-private class ConsumeFlingNestedScrollConnection(
- private val consumeHorizontal: Boolean,
- private val consumeVertical: Boolean,
- private val pagerState: PagerState,
-) : NestedScrollConnection {
- override fun onPostScroll(
- consumed: Offset,
- available: Offset,
- source: NestedScrollSource
- ): Offset = when (source) {
- // We can consume all resting fling scrolls so that they don't propagate up to the
- // Pager
- NestedScrollSource.Fling -> available.consume(consumeHorizontal, consumeVertical)
- else -> Offset.Zero
- }
-
- override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- return if (pagerState.currentPageOffset != 0f) {
- // The Pager is already scrolling. This means that a nested scroll child was
- // scrolled to end, and the Pager can use this fling
- Velocity.Zero
- } else {
- // A nested scroll child is still scrolling. We can consume all post fling
- // velocity on the main-axis so that it doesn't propagate up to the Pager
- available.consume(consumeHorizontal, consumeVertical)
- }
- }
-}
-
-private fun Offset.consume(
- consumeHorizontal: Boolean,
- consumeVertical: Boolean,
-): Offset = Offset(
- x = if (consumeHorizontal) this.x else 0f,
- y = if (consumeVertical) this.y else 0f,
-)
-
-private fun Velocity.consume(
- consumeHorizontal: Boolean,
- consumeVertical: Boolean,
-): Velocity = Velocity(
- x = if (consumeHorizontal) this.x else 0f,
- y = if (consumeVertical) this.y else 0f,
-)
-
-/**
- * Scope for [HorizontalPager] content.
- */
-@Stable
-interface PagerScope {
- /**
- * Returns the current selected page
- */
- val currentPage: Int
-
- /**
- * The current offset from the start of [currentPage], as a ratio of the page width.
- */
- val currentPageOffset: Float
-}
-
-private class PagerScopeImpl(
- private val state: PagerState,
-) : PagerScope {
- override val currentPage: Int get() = state.currentPage
- override val currentPageOffset: Float get() = state.currentPageOffset
-}
-
-/**
- * Calculate the offset for the given [page] from the current scroll position. This is useful
- * when using the scroll position to apply effects or animations to items.
- *
- * The returned offset can positive or negative, depending on whether which direction the [page] is
- * compared to the current scroll position.
- *
- * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition
- */
-fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float {
- return (currentPage - page) + currentPageOffset
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
deleted file mode 100644
index 480335d..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import androidx.annotation.FloatRange
-import androidx.annotation.IntRange
-import androidx.compose.foundation.MutatePriority
-import androidx.compose.foundation.gestures.ScrollScope
-import androidx.compose.foundation.gestures.ScrollableState
-import androidx.compose.foundation.interaction.InteractionSource
-import androidx.compose.foundation.lazy.LazyListItemInfo
-import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.listSaver
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import kotlin.math.abs
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-/**
- * *************************************************************************************************
- * This file was forked from
- * https://github.com/google/accompanist/blob/main/pager/src/main/java/com/google/accompanist/pager/PagerState.kt
- * and will be removed once it lands in AndroidX.
- */
-
-/**
- * Creates a [PagerState] that is remembered across compositions.
- *
- * Changes to the provided values for [initialPage] will **not** result in the state being
- * recreated or changed in any way if it has already
- * been created.
- *
- * @param initialPage the initial value for [PagerState.currentPage]
- */
-@Composable
-fun rememberPagerState(
- @IntRange(from = 0) initialPage: Int = 0,
-): PagerState = rememberSaveable(saver = PagerState.Saver) {
- PagerState(
- currentPage = initialPage,
- )
-}
-
-/**
- * A state object that can be hoisted to control and observe scrolling for [HorizontalPager].
- *
- * In most cases, this will be created via [rememberPagerState].
- *
- * @param currentPage the initial value for [PagerState.currentPage]
- */
-@Stable
-class PagerState(
- @IntRange(from = 0) currentPage: Int = 0,
-) : ScrollableState {
- // Should this be public?
- internal val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
-
- private var _currentPage by mutableStateOf(currentPage)
-
- // finds the page which has larger visible area within the viewport not including paddings
- internal val mostVisiblePageLayoutInfo: LazyListItemInfo?
- get() {
- val layoutInfo = lazyListState.layoutInfo
- return layoutInfo.visibleItemsInfo.maxByOrNull {
- val start = maxOf(it.offset, 0)
- val end = minOf(
- it.offset + it.size,
- layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding
- )
- end - start
- }
- }
-
- internal var itemSpacing by mutableStateOf(0)
-
- private val currentPageLayoutInfo: LazyListItemInfo?
- get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
- it.index == currentPage
- }
-
- /**
- * [InteractionSource] that will be used to dispatch drag events when this
- * list is being dragged. If you want to know whether the fling (or animated scroll) is in
- * progress, use [isScrollInProgress].
- */
- val interactionSource: InteractionSource
- get() = lazyListState.interactionSource
-
- /**
- * The number of pages to display.
- */
- @get:IntRange(from = 0)
- val pageCount: Int by derivedStateOf {
- lazyListState.layoutInfo.totalItemsCount
- }
-
- /**
- * The index of the currently selected page. This may not be the page which is
- * currently displayed on screen.
- *
- * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
- */
- @get:IntRange(from = 0)
- var currentPage: Int
- get() = _currentPage
- internal set(value) {
- if (value != _currentPage) {
- _currentPage = value
- }
- }
-
- /**
- * The current offset from the start of [currentPage], as a ratio of the page width.
- *
- * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
- */
- val currentPageOffset: Float by derivedStateOf {
- currentPageLayoutInfo?.let {
- (-it.offset / (it.size + itemSpacing).toFloat()).coerceIn(-0.5f, 0.5f)
- } ?: 0f
- }
-
- /**
- * The target page for any on-going animations.
- */
- private var animationTargetPage: Int? by mutableStateOf(null)
-
- /**
- * Animate (smooth scroll) to the given page to the middle of the viewport.
- *
- * Cancels the currently running scroll, if any, and suspends until the cancellation is
- * complete.
- *
- * @param page the page to animate to. Must be >= 0.
- * @param pageOffset the percentage of the page size to offset, from the start of [page].
- * Must be in the range -1f..1f.
- */
- suspend fun animateScrollToPage(
- @IntRange(from = 0) page: Int,
- @FloatRange(from = -1.0, to = 1.0) pageOffset: Float = 0f,
- ) {
- requireCurrentPage(page, "page")
- requireCurrentPageOffset(pageOffset, "pageOffset")
- try {
- animationTargetPage = page
-
- // pre-jump to nearby item for long jumps as an optimization
- // the same trick is done in ViewPager2
- val oldPage = lazyListState.firstVisibleItemIndex
- if (abs(page - oldPage) > 3) {
- lazyListState.scrollToItem(if (page > oldPage) page - 3 else page + 3)
- }
-
- if (pageOffset.absoluteValue <= 0.005f) {
- // If the offset is (close to) zero, just call animateScrollToItem and we're done
- lazyListState.animateScrollToItem(index = page)
- } else {
- // Else we need to figure out what the offset is in pixels...
- lazyListState.scroll { } // this will await for the first layout.
- val layoutInfo = lazyListState.layoutInfo
- var target = layoutInfo.visibleItemsInfo
- .firstOrNull { it.index == page }
-
- if (target != null) {
- // If we have access to the target page layout, we can calculate the pixel
- // offset from the size
- lazyListState.animateScrollToItem(
- index = page,
- scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
- )
- } else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
- // If we don't, we use the current page size as a guide
- val currentSize = layoutInfo.visibleItemsInfo.first().size + itemSpacing
- lazyListState.animateScrollToItem(
- index = page,
- scrollOffset = (currentSize * pageOffset).roundToInt()
- )
-
- // The target should be visible now
- target = layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
-
- if (target != null && target.size + itemSpacing != currentSize) {
- // If the size we used for calculating the offset differs from the actual
- // target page size, we need to scroll again. This doesn't look great,
- // but there's not much else we can do.
- lazyListState.animateScrollToItem(
- index = page,
- scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
- )
- }
- }
- }
- } finally {
- // We need to manually call this, as the `animateScrollToItem` call above will happen
- // in 1 frame, which is usually too fast for the LaunchedEffect in Pager to detect
- // the change. This is especially true when running unit tests.
- onScrollFinished()
- }
- }
-
- /**
- * Instantly brings the item at [page] to the middle of the viewport.
- *
- * Cancels the currently running scroll, if any, and suspends until the cancellation is
- * complete.
- *
- * @param page the page to snap to. Must be >= 0.
- * @param pageOffset the percentage of the page size to offset, from the start of [page].
- * Must be in the range -1f..1f.
- */
- suspend fun scrollToPage(
- @IntRange(from = 0) page: Int,
- @FloatRange(from = -1.0, to = 1.0) pageOffset: Float = 0f,
- ) {
- requireCurrentPage(page, "page")
- requireCurrentPageOffset(pageOffset, "pageOffset")
- try {
- animationTargetPage = page
-
- // First scroll to the given page. It will now be laid out at offset 0
- lazyListState.scrollToItem(index = page)
- updateCurrentPageBasedOnLazyListState()
-
- // If we have a start spacing, we need to offset (scroll) by that too
- if (pageOffset.absoluteValue > 0.0001f) {
- currentPageLayoutInfo?.let {
- scroll {
- scrollBy((it.size + itemSpacing) * pageOffset)
- }
- }
- }
- } finally {
- // We need to manually call this, as the `scroll` call above will happen in 1 frame,
- // which is usually too fast for the LaunchedEffect in Pager to detect the change.
- // This is especially true when running unit tests.
- onScrollFinished()
- }
- }
-
- internal fun updateCurrentPageBasedOnLazyListState() {
- // Then update the current page to our layout page
- mostVisiblePageLayoutInfo?.let {
- currentPage = it.index
- }
- }
-
- internal fun onScrollFinished() {
- // Clear the animation target page
- animationTargetPage = null
- }
-
- override suspend fun scroll(
- scrollPriority: MutatePriority,
- block: suspend ScrollScope.() -> Unit
- ) = lazyListState.scroll(scrollPriority, block)
-
- override fun dispatchRawDelta(delta: Float): Float {
- return lazyListState.dispatchRawDelta(delta)
- }
-
- override val isScrollInProgress: Boolean
- get() = lazyListState.isScrollInProgress
-
- override fun toString(): String = "PagerState(" +
- "pageCount=$pageCount, " +
- "currentPage=$currentPage, " +
- "currentPageOffset=$currentPageOffset" +
- ")"
-
- private fun requireCurrentPage(value: Int, name: String) {
- require(value >= 0) { "$name[$value] must be >= 0" }
- }
-
- private fun requireCurrentPageOffset(value: Float, name: String) {
- require(value in -1f..1f) { "$name must be >= -1 and <= 1" }
- }
-
- companion object {
- /**
- * The default [Saver] implementation for [PagerState].
- */
- val Saver: Saver<PagerState, *> = listSaver(
- save = {
- listOf<Any>(
- it.currentPage,
- )
- },
- restore = {
- PagerState(
- currentPage = it[0] as Int,
- )
- }
- )
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index e6fa74e..26372b6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -17,6 +17,9 @@
package com.android.settingslib.spa.framework.theme
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.ripple.LocalRippleTheme
+import androidx.compose.material.ripple.RippleAlpha
+import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -33,8 +36,11 @@
background = settingsColorScheme.background,
)
- CompositionLocalProvider(LocalColorScheme provides settingsColorScheme(isDarkTheme)) {
- MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+ MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+ CompositionLocalProvider(
+ LocalColorScheme provides settingsColorScheme(isDarkTheme),
+ LocalRippleTheme provides SettingsRippleTheme,
+ ) {
content()
}
}
@@ -46,3 +52,19 @@
@ReadOnlyComposable
get() = LocalColorScheme.current
}
+
+private object SettingsRippleTheme : RippleTheme {
+ @Composable
+ override fun defaultColor() = MaterialTheme.colorScheme.onSurface
+
+ @Composable
+ override fun rippleAlpha() = RippleAlpha
+}
+
+/** Alpha levels for all content. */
+private val RippleAlpha = RippleAlpha(
+ pressedAlpha = 0.48f,
+ focusedAlpha = 0.48f,
+ draggedAlpha = 0.32f,
+ hoveredAlpha = 0.16f,
+)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
index 83dcd13..780933d3c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -43,6 +43,7 @@
/** Enum to define all column names in provider. */
enum class ColumnEnum(val id: String) {
ENTRY_ID("entryId"),
+ ENTRY_LABEL("entryLabel"),
SEARCH_TITLE("searchTitle"),
SEARCH_KEYWORD("searchKw"),
SEARCH_PATH("searchPath"),
@@ -50,7 +51,6 @@
INTENT_TARGET_CLASS("intentTargetClass"),
INTENT_EXTRAS("intentExtras"),
SLICE_URI("sliceUri"),
- LEGACY_KEY("legacyKey"),
ENTRY_DISABLED("entryDisabled"),
}
@@ -64,6 +64,7 @@
SEARCH_STATIC_DATA,
listOf(
ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.SEARCH_TITLE,
ColumnEnum.SEARCH_KEYWORD,
ColumnEnum.SEARCH_PATH,
@@ -71,13 +72,13 @@
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
ColumnEnum.SLICE_URI,
- ColumnEnum.LEGACY_KEY
)
),
SEARCH_DYNAMIC_DATA_QUERY(
SEARCH_DYNAMIC_DATA,
listOf(
ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.SEARCH_TITLE,
ColumnEnum.SEARCH_KEYWORD,
ColumnEnum.SEARCH_PATH,
@@ -85,13 +86,13 @@
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
ColumnEnum.SLICE_URI,
- ColumnEnum.LEGACY_KEY
)
),
SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
SEARCH_IMMUTABLE_STATUS,
listOf(
ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.ENTRY_DISABLED,
)
),
@@ -99,6 +100,7 @@
SEARCH_MUTABLE_STATUS,
listOf(
ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.ENTRY_DISABLED,
)
),
@@ -106,6 +108,7 @@
SEARCH_STATIC_ROW,
listOf(
ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.SEARCH_TITLE,
ColumnEnum.SEARCH_KEYWORD,
ColumnEnum.SEARCH_PATH,
@@ -113,7 +116,6 @@
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
ColumnEnum.SLICE_URI,
- ColumnEnum.LEGACY_KEY,
ColumnEnum.ENTRY_DISABLED,
)
),
@@ -121,6 +123,7 @@
SEARCH_DYNAMIC_ROW,
listOf(
ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_LABEL,
ColumnEnum.SEARCH_TITLE,
ColumnEnum.SEARCH_KEYWORD,
ColumnEnum.SEARCH_PATH,
@@ -128,7 +131,6 @@
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
ColumnEnum.SLICE_URI,
- ColumnEnum.LEGACY_KEY,
ColumnEnum.ENTRY_DISABLED,
)
),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 057f13f..eacb28c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -42,7 +42,7 @@
* One can query the provider result by:
* $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
* For gallery, AuthorityPath = com.android.spa.gallery.search.provider
- * For Settings, AuthorityPath = com.android.settings.spa.search.provider"
+ * For Settings, AuthorityPath = com.android.settings.spa.search.provider
* Some examples:
* $ adb shell content query --uri content://<AuthorityPath>/search_static_data
* $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_data
@@ -205,6 +205,7 @@
val searchData = entry.getSearchData() ?: return
val intent = entry.createIntent(SESSION_SEARCH)
val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
+ .add(ColumnEnum.ENTRY_LABEL.id, entry.label)
.add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
.add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
.add(
@@ -221,7 +222,6 @@
ColumnEnum.SLICE_URI.id, Uri.Builder()
.fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
)
- // TODO: support legacy key
}
private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
@@ -229,6 +229,7 @@
val statusData = entry.getStatusData() ?: return
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
+ .add(ColumnEnum.ENTRY_LABEL.id, entry.label)
.add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
}
@@ -239,6 +240,7 @@
val searchData = entry.getSearchData() ?: return
val intent = entry.createIntent(SESSION_SEARCH)
val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
+ .add(ColumnEnum.ENTRY_LABEL.id, entry.label)
.add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
.add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
.add(
@@ -255,8 +257,6 @@
ColumnEnum.SLICE_URI.id, Uri.Builder()
.fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
)
- // TODO: support legacy key
-
// Fetch status data. We can add runtime arguments later if necessary
val statusData = entry.getStatusData() ?: return
row.add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt
index b8c59ad..7f7088a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt
@@ -163,7 +163,7 @@
Text(
text = data,
color = MaterialTheme.colorScheme.onSurfaceVariant,
- style = MaterialTheme.typography.titleMedium,
+ style = MaterialTheme.typography.titleSmall,
)
}
subTitle()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index e0e9b95..c6e13a1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -16,19 +16,21 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.TabRow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import com.android.settingslib.spa.framework.compose.HorizontalPager
-import com.android.settingslib.spa.framework.compose.rememberPagerState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import kotlin.math.absoluteValue
import kotlinx.coroutines.launch
+@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit) {
check(titles.isNotEmpty())
@@ -52,7 +54,7 @@
SettingsTab(
title = title,
selected = pagerState.currentPage == page,
- currentPageOffset = pagerState.currentPageOffset.absoluteValue,
+ currentPageOffset = pagerState.currentPageOffsetFraction.absoluteValue,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(page)
@@ -62,7 +64,7 @@
}
}
- HorizontalPager(count = titles.size, state = pagerState) { page ->
+ HorizontalPager(pageCount = titles.size, state = pagerState) { page ->
content(page)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index 64a9c73..f0df9a6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -23,8 +23,8 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.ArrowDropDown
-import androidx.compose.material.icons.outlined.ArrowDropUp
+import androidx.compose.material.icons.outlined.ExpandLess
+import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
@@ -76,8 +76,8 @@
SpinnerText(options.find { it.id == selectedId })
Icon(
imageVector = when {
- expanded -> Icons.Outlined.ArrowDropUp
- else -> Icons.Outlined.ArrowDropDown
+ expanded -> Icons.Outlined.ExpandLess
+ else -> Icons.Outlined.ExpandMore
},
contentDescription = null,
)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index 379b9a7..b139f28 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -115,7 +115,7 @@
fun testGetEntryPath() {
SpaEnvironmentFactory.reset(spaEnvironment)
assertThat(
- entryRepository.getEntryPathWithDisplayName(
+ entryRepository.getEntryPathWithLabel(
genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
@@ -129,7 +129,7 @@
).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
assertThat(
- entryRepository.getEntryPathWithDisplayName(
+ entryRepository.getEntryPathWithLabel(
genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 5754c9b..ce34979 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -68,7 +68,7 @@
val owner = createSettingsPage("mySpp")
val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
- assertThat(entry.displayName).isEqualTo("myEntry")
+ assertThat(entry.label).isEqualTo("myEntry")
assertThat(entry.owner.sppName).isEqualTo("mySpp")
assertThat(entry.owner.displayName).isEqualTo("mySpp")
assertThat(entry.fromPage).isNull()
@@ -87,14 +87,14 @@
val entryFrom =
SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
assertThat(entryFrom.id).isEqualTo(genEntryId("myEntry", owner, owner, toPage))
- assertThat(entryFrom.displayName).isEqualTo("myEntry")
+ assertThat(entryFrom.label).isEqualTo("myEntry")
assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
val entryTo =
SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
assertThat(entryTo.id).isEqualTo(genEntryId("myEntry", owner, fromPage, owner))
- assertThat(entryTo.displayName).isEqualTo("myEntry")
+ assertThat(entryTo.label).isEqualTo("myEntry")
assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
}
@@ -108,7 +108,7 @@
INJECT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
- assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME_TEST}_mySpp")
+ assertThat(entryInject.label).isEqualTo("${INJECT_ENTRY_NAME_TEST}_mySpp")
assertThat(entryInject.fromPage).isNull()
assertThat(entryInject.toPage).isNotNull()
}
@@ -122,7 +122,7 @@
ROOT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
- assertThat(entryInject.displayName).isEqualTo("myRootEntry")
+ assertThat(entryInject.label).isEqualTo("myRootEntry")
assertThat(entryInject.fromPage).isNull()
assertThat(entryInject.toPage).isNotNull()
}
@@ -133,14 +133,14 @@
val owner = createSettingsPage("SppHome")
val entryBuilder =
SettingsEntryBuilder.create(owner, "myEntry")
- .setDisplayName("myEntryDisplay")
+ .setLabel("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
- assertThat(entry.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entry.label).isEqualTo("myEntryDisplay")
assertThat(entry.fromPage).isNull()
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isTrue()
@@ -152,14 +152,14 @@
val ownerDisabled = createSettingsPage("SppDisabled")
val entryBuilderDisabled =
SettingsEntryBuilder.create(ownerDisabled, "myEntry")
- .setDisplayName("myEntryDisplay")
+ .setLabel("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entryDisabled = entryBuilderDisabled.build()
assertThat(entryDisabled.id).isEqualTo(genEntryId("myEntry", ownerDisabled))
- assertThat(entryDisabled.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entryDisabled.label).isEqualTo("myEntryDisplay")
assertThat(entryDisabled.fromPage).isNull()
assertThat(entryDisabled.toPage).isNull()
assertThat(entryDisabled.isAllowSearch).isFalse()
@@ -175,7 +175,7 @@
SpaEnvironmentFactory.reset()
val entry3 = entryBuilder.build()
assertThat(entry3.id).isEqualTo(genEntryId("myEntry", owner))
- assertThat(entry3.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entry3.label).isEqualTo("myEntryDisplay")
assertThat(entry3.fromPage).isNull()
assertThat(entry3.toPage).isNull()
assertThat(entry3.isAllowSearch).isFalse()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
index 007d08b..00d2314 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -63,6 +63,11 @@
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
)
immutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchDynamicWithImmutableStatus")
+ )
+ immutableStatus.checkValue(
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
@@ -75,6 +80,11 @@
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
mutableStatus.checkValue(
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchStaticWithMutableStatus")
+ )
+ mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
)
@@ -85,6 +95,11 @@
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
mutableStatus.checkValue(
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchDynamicWithMutableStatus")
+ )
+ mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
}
@@ -102,6 +117,11 @@
pageOwner.getEntryId("SearchStaticWithNoStatus")
)
staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchStaticWithNoStatus")
+ )
+ staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
)
staticData.checkValue(
@@ -139,6 +159,11 @@
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchStaticWithMutableStatus")
+ )
val dynamicData = searchProvider.querySearchDynamicData()
Truth.assertThat(dynamicData.count).isEqualTo(2)
@@ -148,6 +173,11 @@
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
+ dynamicData.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchDynamicWithMutableStatus")
+ )
dynamicData.moveToNext()
dynamicData.checkValue(
@@ -157,6 +187,11 @@
)
dynamicData.checkValue(
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchDynamicWithImmutableStatus")
+ )
+ dynamicData.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
ColumnEnum.SEARCH_KEYWORD,
listOf("kw1", "kw2").toString()
)
@@ -175,6 +210,11 @@
pageOwner.getEntryId("SearchStaticWithNoStatus")
)
staticRow.checkValue(
+ QueryEnum.SEARCH_STATIC_ROW_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchStaticWithNoStatus")
+ )
+ staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
)
staticRow.checkValue(
@@ -223,6 +263,11 @@
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
dynamicRow.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchStaticWithMutableStatus")
+ )
+ dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
)
@@ -233,6 +278,11 @@
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
dynamicRow.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchDynamicWithMutableStatus")
+ )
+ dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
@@ -245,6 +295,11 @@
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+ ColumnEnum.ENTRY_LABEL,
+ pageOwner.getEntryLabel("SearchDynamicWithImmutableStatus")
+ )
+ dynamicRow.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.SEARCH_KEYWORD,
listOf("kw1", "kw2").toString()
)
@@ -271,3 +326,7 @@
private fun SettingsPage.getEntryId(name: String): String {
return SettingsEntryBuilder.create(this, name).build().id
}
+
+private fun SettingsPage.getEntryLabel(name: String): String {
+ return SettingsEntryBuilder.create(this, name).build().label
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
index 14cb698f..e37288ab 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -81,7 +81,7 @@
val entryList = appInfoPageProvider.buildEntry(null)
assertThat(entryList).hasSize(1)
- assertThat(entryList[0].displayName).isEqualTo("AllowControl")
+ assertThat(entryList[0].label).isEqualTo("AllowControl")
}
@Test
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 9e86567..eca1165 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -23,6 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.cellbroadcast",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index e9c6aed..a3e50a9 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -23,6 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index dc88304..644e990 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -26,6 +26,6 @@
"com.android.adservices",
"com.android.permission",
"com.android.cellbroadcast",
- "com.android.healthconnect",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 6641db1..91b852a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -235,7 +235,7 @@
/**
* @return whether high quality audio is enabled or not
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
if (bluetoothDevice == null) {
@@ -287,7 +287,7 @@
* @param device to get codec label from
* @return the label associated with the device codec
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index e0588ee..2555e2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -34,6 +34,8 @@
import com.android.settingslib.R;
+import java.util.Optional;
+
/**
* Stores and computes some battery information.
*/
@@ -52,11 +54,12 @@
public final int health;
public final int maxChargingWattage;
public final boolean present;
+ public final Optional<Boolean> incompatibleCharger;
- public static BatteryStatus create(Context context) {
+ public static BatteryStatus create(Context context, boolean incompatibleCharger) {
final Intent batteryChangedIntent = BatteryUtils.getBatteryIntent(context);
return batteryChangedIntent == null
- ? null : new BatteryStatus(batteryChangedIntent);
+ ? null : new BatteryStatus(batteryChangedIntent, incompatibleCharger);
}
public BatteryStatus(int status, int level, int plugged, int health,
@@ -67,14 +70,25 @@
this.health = health;
this.maxChargingWattage = maxChargingWattage;
this.present = present;
+ this.incompatibleCharger = Optional.empty();
}
+
public BatteryStatus(Intent batteryChangedIntent) {
+ this(batteryChangedIntent, Optional.empty());
+ }
+
+ public BatteryStatus(Intent batteryChangedIntent, boolean incompatibleCharger) {
+ this(batteryChangedIntent, Optional.of(incompatibleCharger));
+ }
+
+ private BatteryStatus(Intent batteryChangedIntent, Optional<Boolean> incompatibleCharger) {
status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
level = getBatteryLevel(batteryChangedIntent);
health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
+ this.incompatibleCharger = incompatibleCharger;
final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
-1);
@@ -95,10 +109,7 @@
/** Determine whether the device is plugged. */
public boolean isPluggedIn() {
- return plugged == BatteryManager.BATTERY_PLUGGED_AC
- || plugged == BatteryManager.BATTERY_PLUGGED_USB
- || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
- || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+ return isPluggedIn(plugged);
}
/** Determine whether the device is plugged in (USB, power). */
@@ -190,4 +201,17 @@
? -1 /*invalid battery level*/
: Math.round((level / (float) scale) * 100f);
}
+
+ /** Whether the device is plugged or not. */
+ public static boolean isPluggedIn(Intent batteryChangedIntent) {
+ return isPluggedIn(batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0));
+ }
+
+ /** Whether the device is plugged or not. */
+ public static boolean isPluggedIn(int plugged) {
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB
+ || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
+ || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index f83ebaa..720d6dd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -26,16 +26,23 @@
import android.content.IContentProvider;
import android.os.Binder;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.provider.DeviceConfig;
+import android.provider.DeviceConfigShellCommandHandler;
import android.provider.Settings;
import android.provider.Settings.Config.SyncDisabledMode;
+import android.provider.UpdatableDeviceConfigServiceReadiness;
+
+import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -56,8 +63,32 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- (new MyShellCommand(mProvider)).exec(this, in, out, err, args, callback, resultReceiver);
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
+ callUpdableDeviceConfigShellCommandHandler(in, out, err, args, resultReceiver);
+ } else {
+ (new MyShellCommand(mProvider))
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ }
+
+ private void callUpdableDeviceConfigShellCommandHandler(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ int result = -1;
+ try (
+ ParcelFileDescriptor inPfd = ParcelFileDescriptor.dup(in);
+ ParcelFileDescriptor outPfd = ParcelFileDescriptor.dup(out);
+ ParcelFileDescriptor errPfd = ParcelFileDescriptor.dup(err)) {
+ result = DeviceConfigShellCommandHandler.handleShellCommand(inPfd, outPfd, errPfd,
+ args);
+ } catch (IOException e) {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(err));
+ pw.println("dup() failed: " + e.getMessage());
+ pw.flush();
+ } finally {
+ resultReceiver.send(result, null);
+ }
}
static final class MyShellCommand extends ShellCommand {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f1413e5..7abace03 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -97,7 +97,6 @@
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.provider.Settings.SetAllResult;
-import android.provider.UpdatableDeviceConfigServiceReadiness;
import android.provider.settings.validators.SystemSettingsValidators;
import android.provider.settings.validators.Validator;
import android.text.TextUtils;
@@ -419,16 +418,10 @@
startWatchingUserRestrictionChanges();
});
ServiceManager.addService("settings", new SettingsService(this));
- addDeviceConfigServiceIfNeeded();
+ ServiceManager.addService("device_config", new DeviceConfigService(this));
return true;
}
- private void addDeviceConfigServiceIfNeeded() {
- if (!UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
- ServiceManager.addService("device_config", new DeviceConfigService(this));
- }
- }
-
@Override
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index fdab749..a08b598 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,6 +23,7 @@
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
+import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.SparseArray
@@ -215,13 +216,40 @@
textInterpolator.targetPaint.textSize = textSize
}
if (weight >= 0) {
- // Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
- // memory impact, cache the typeface result.
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
- textInterpolator.targetPaint.typeface
+ val fontVariationArray =
+ FontVariationAxis.fromFontVariationSettings(
+ textInterpolator.targetPaint.fontVariationSettings
+ )
+ if (fontVariationArray.isNullOrEmpty()) {
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.typeface
+ }
+ } else {
+ val idx = fontVariationArray.indexOfFirst { it.tag == "$TAG_WGHT" }
+ if (idx == -1) {
+ val updatedFontVariation =
+ textInterpolator.targetPaint.fontVariationSettings + ",'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings =
+ updatedFontVariation
+ textInterpolator.targetPaint.typeface
+ }
+ } else {
+ fontVariationArray[idx] = FontVariationAxis(
+ "$TAG_WGHT", weight.toFloat())
+ val updatedFontVariation =
+ FontVariationAxis.toFontVariationSettings(fontVariationArray)
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings =
+ updatedFontVariation
+ textInterpolator.targetPaint.typeface
+ }
}
+ }
}
if (color != null) {
textInterpolator.targetPaint.color = color
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f4cef84..51f507c 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -55,6 +55,9 @@
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
<string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string>
+ <!-- When the lock screen is showing and the phone plugged in with incompatible charger. -->
+ <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Incompatible charging</string>
+
<!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. -->
<string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res/drawable/ic_progress_activity.xml b/packages/SystemUI/res/drawable/ic_progress_activity.xml
new file mode 100644
index 0000000..abf0625
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_progress_activity.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M24,44Q19.8,44 16.15,42.45Q12.5,40.9 9.8,38.2Q7.1,35.5 5.55,31.85Q4,28.2 4,24Q4,19.8 5.55,16.15Q7.1,12.5 9.8,9.8Q12.5,7.1 16.15,5.55Q19.8,4 24,4Q24.6,4 25.05,4.45Q25.5,4.9 25.5,5.5Q25.5,6.1 25.05,6.55Q24.6,7 24,7Q16.95,7 11.975,11.975Q7,16.95 7,24Q7,31.05 11.975,36.025Q16.95,41 24,41Q31.05,41 36.025,36.025Q41,31.05 41,24Q41,23.4 41.45,22.95Q41.9,22.5 42.5,22.5Q43.1,22.5 43.55,22.95Q44,23.4 44,24Q44,28.2 42.45,31.85Q40.9,35.5 38.2,38.2Q35.5,40.9 31.85,42.45Q28.2,44 24,44Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
new file mode 100644
index 0000000..1b12e74
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
@@ -0,0 +1,25 @@
+<!--
+ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?android:attr/textColorSecondary">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M39.8,41.95 L26.65,28.8Q25.15,30.1 23.15,30.825Q21.15,31.55 18.9,31.55Q13.5,31.55 9.75,27.8Q6,24.05 6,18.75Q6,13.45 9.75,9.7Q13.5,5.95 18.85,5.95Q24.15,5.95 27.875,9.7Q31.6,13.45 31.6,18.75Q31.6,20.9 30.9,22.9Q30.2,24.9 28.8,26.65L42,39.75ZM18.85,28.55Q22.9,28.55 25.75,25.675Q28.6,22.8 28.6,18.75Q28.6,14.7 25.75,11.825Q22.9,8.95 18.85,8.95Q14.75,8.95 11.875,11.825Q9,14.7 9,18.75Q9,22.8 11.875,25.675Q14.75,28.55 18.85,28.55Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_shortcutlist_search_button_cancel.xml b/packages/SystemUI/res/drawable/ic_shortcutlist_search_button_cancel.xml
new file mode 100644
index 0000000..e3b1ab2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_shortcutlist_search_button_cancel.xml
@@ -0,0 +1,25 @@
+<!--
+ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/textColorSecondary">
+ <path
+ android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+ android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shortcut_button_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
new file mode 100644
index 0000000..a21489c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp"/>
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
new file mode 100644
index 0000000..2ff32b6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
new file mode 100644
index 0000000..6ce3eae
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground"/>
+ <corners android:topLeftRadius="16dp"
+ android:topRightRadius="16dp"
+ android:bottomLeftRadius="0dp"
+ android:bottomRightRadius="0dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shortcut_search_background.xml b/packages/SystemUI/res/drawable/shortcut_search_background.xml
new file mode 100644
index 0000000..66fc191
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shortcut_search_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurface" />
+ <corners android:radius="24dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 8cf4f4d..0ff944c 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -60,14 +60,13 @@
/>
<!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
- <ProgressBar
+ <ImageView
android:id="@+id/loading"
- android:indeterminate="true"
android:layout_width="@dimen/media_ttt_status_icon_size"
android:layout_height="@dimen/media_ttt_status_icon_size"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
- android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
- style="?android:attr/progressBarStyleSmall"
+ android:src="@drawable/ic_progress_activity"
+ android:tint="?androidprv:attr/colorAccentPrimaryVariant"
android:alpha="0.0"
/>
diff --git a/packages/SystemUI/res/layout/font_scaling_dialog.xml b/packages/SystemUI/res/layout/font_scaling_dialog.xml
new file mode 100644
index 0000000..27c1e9d
--- /dev/null
+++ b/packages/SystemUI/res/layout/font_scaling_dialog.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.
+ -->
+<com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/font_scaling_slider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:max="6"
+ app:progress="0"
+ app:iconStartContentDescription="@string/font_scaling_smaller"
+ app:iconEndContentDescription="@string/font_scaling_larger"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
new file mode 100644
index 0000000..530e46e
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="0dp"
+ style="@style/ShortcutHorizontalDivider" />
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml
new file mode 100644
index 0000000..a037cb2
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/ksh_item_padding"
+ android:layout_marginLeft="0dp"
+ android:layout_marginRight="0dp"
+ android:scaleType="fitXY"
+ android:tint="?android:attr/textColorPrimary"
+ style="@style/ShortcutItemBackground" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml
new file mode 100644
index 0000000..12b4e15
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/ksh_item_padding"
+ android:layout_marginStart="@dimen/ksh_item_margin_start"
+ style="@style/ShortcutItemBackground"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="false"
+ android:gravity="center"
+ android:textSize="@dimen/ksh_item_text_size"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml
new file mode 100644
index 0000000..727f2c1
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/ksh_item_padding"
+ android:layout_marginLeft="0dp"
+ android:layout_marginRight="0dp"
+ android:text="+"
+ style="@style/ShortcutItemBackground"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="@dimen/ksh_item_text_size"
+ android:textAllCaps="true"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml
new file mode 100644
index 0000000..00ef947
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/ksh_item_padding"
+ android:layout_marginLeft="0dp"
+ android:layout_marginRight="0dp"
+ android:text="|"
+ style="@style/ShortcutItemBackground"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="@dimen/ksh_item_text_size"
+ android:textAllCaps="true"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
new file mode 100644
index 0000000..8a66f50
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@drawable/shortcut_dialog_bg"
+ android:layout_width="@dimen/ksh_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/shortcut_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="40dp"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/keyboard_shortcut_search_list_title"/>
+
+ <FrameLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <EditText
+ android:id="@+id/keyboard_shortcuts_search"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="24dp"
+ android:layout_marginStart="49dp"
+ android:layout_marginEnd="49dp"
+ android:padding="16dp"
+ android:background="@drawable/shortcut_search_background"
+ android:drawableStart="@drawable/ic_shortcutlist_search"
+ android:drawablePadding="15dp"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:inputType="text"
+ android:textDirection="locale"
+ android:textAlignment="viewStart"
+ android:hint="@string/keyboard_shortcut_search_list_hint"
+ android:textColorHint="?android:attr/textColorTertiary" />
+
+ <ImageView
+ android:id="@+id/keyboard_shortcuts_search_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="24dp"
+ android:layout_marginEnd="49dp"
+ android:padding="16dp"
+ android:contentDescription="@string/keyboard_shortcut_clear_text"
+ android:src="@drawable/ic_shortcutlist_search_button_cancel" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <View
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="37dp"/>
+
+ <Button
+ android:id="@+id/shortcut_system"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ style="@style/ShortCutButton"
+ android:text="@string/keyboard_shortcut_search_category_system"/>
+
+ <Button
+ android:id="@+id/shortcut_input"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ style="@style/ShortCutButton"
+ android:text="@string/keyboard_shortcut_search_category_input"/>
+
+ <Button
+ android:id="@+id/shortcut_open_apps"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ style="@style/ShortCutButton"
+ android:text="@string/keyboard_shortcut_search_category_open_apps"/>
+
+ <Button
+ android:id="@+id/shortcut_specific_app"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ style="@style/ShortCutButton"
+ android:text="@string/keyboard_shortcut_search_category_current_app"/>
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/shortcut_search_no_result"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="50dp"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/keyboard_shortcut_search_list_no_result"/>
+
+ <ScrollView
+ android:id="@+id/keyboard_shortcuts_scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="25dp"
+ android:layout_marginEnd="25dp">
+ <LinearLayout
+ android:id="@+id/keyboard_shortcuts_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"/>
+ </ScrollView>
+ <!-- Required for stretching to full available height when the items in the scroll view
+ occupy less space then the full height -->
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
index f20b582..52d1d4f 100644
--- a/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
+++ b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
@@ -1,41 +1,47 @@
<?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.
- -->
+ 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.
+ -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/seekbar_frame"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- tools:parentTag="android.widget.LinearLayout">
+ android:id="@+id/seekbar_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ tools:parentTag="android.widget.LinearLayout">
- <ImageView
- android:id="@+id/icon_start"
- android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
- android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
- android:layout_gravity="center"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:adjustViewBounds="true"
- android:focusable="true"
- android:src="@drawable/ic_remove"
- android:tint="?android:attr/textColorPrimary"
- android:tintMode="src_in" />
+ <FrameLayout
+ android:id="@+id/icon_start_frame"
+ android:layout_width="@dimen/min_clickable_item_size"
+ android:layout_height="@dimen/min_clickable_item_size"
+ android:clipChildren="false"
+ android:focusable="true" >
+ <ImageView
+ android:id="@+id/icon_start"
+ android:layout_width="@dimen/seekbar_icon_size"
+ android:layout_height="@dimen/seekbar_icon_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:adjustViewBounds="true"
+ android:focusable="false"
+ android:src="@drawable/ic_remove"
+ android:tint="?android:attr/textColorPrimary"
+ android:tintMode="src_in" />
+ </FrameLayout>
<SeekBar
android:id="@+id/seekbar"
@@ -45,16 +51,23 @@
android:layout_gravity="center_vertical"
android:layout_weight="1" />
- <ImageView
- android:id="@+id/icon_end"
- android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
- android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
- android:layout_gravity="center"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:adjustViewBounds="true"
- android:focusable="true"
- android:src="@drawable/ic_add"
- android:tint="?android:attr/textColorPrimary"
- android:tintMode="src_in" />
+ <FrameLayout
+ android:id="@+id/icon_end_frame"
+ android:layout_width="@dimen/min_clickable_item_size"
+ android:layout_height="@dimen/min_clickable_item_size"
+ android:clipChildren="false"
+ android:focusable="true" >
+ <ImageView
+ android:id="@+id/icon_end"
+ android:layout_width="@dimen/seekbar_icon_size"
+ android:layout_height="@dimen/seekbar_icon_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:adjustViewBounds="true"
+ android:focusable="false"
+ android:src="@drawable/ic_add"
+ android:tint="?android:attr/textColorPrimary"
+ android:tintMode="src_in" />
+ </FrameLayout>
</merge>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index e2fdf16..c6cc0bc 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -118,6 +118,7 @@
<color name="ksh_application_group_color">#fff44336</color>
<color name="ksh_key_item_color">@color/material_grey_600</color>
<color name="ksh_key_item_background">@color/material_grey_100</color>
+ <color name="ksh_key_item_new_background">@color/transparent</color>
<color name="instant_apps_color">#ff4d5a64</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7716fa9..12e5f19 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -659,7 +659,7 @@
<item>26</item> <!-- MOUTH_COVERING_DETECTED -->
</integer-array>
- <!-- Which device wake-ups will trigger face auth. These values correspond with
+ <!-- Which device wake-ups will trigger passive auth. These values correspond with
PowerManager#WakeReason. -->
<integer-array name="config_face_auth_wake_up_triggers">
<item>1</item> <!-- WAKE_REASON_POWER_BUTTON -->
@@ -668,6 +668,7 @@
<item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
<item>9</item> <!-- WAKE_REASON_LID -->
<item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
+ <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
<item>15</item> <!-- WAKE_REASON_TAP -->
<item>16</item> <!-- WAKE_REASON_LIFT -->
<item>17</item> <!-- WAKE_REASON_BIOMETRIC -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 36172ca..4afe9d5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1183,8 +1183,9 @@
<dimen name="magnification_setting_image_button_padding_horizontal">24dp</dimen>
<dimen name="magnification_setting_image_button_open_in_full_padding_vertical">16dp</dimen>
<dimen name="magnification_setting_image_button_open_in_full_padding_horizontal">28dp</dimen>
- <dimen name="magnification_setting_seekbar_icon_size">24dp</dimen>
+ <!-- Seekbar with icon buttons -->
+ <dimen name="seekbar_icon_size">24dp</dimen>
<!-- How far from the right edge of the screen you need to drag the window before the button
repositions to the other side. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 85edbec..754433b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1700,34 +1700,105 @@
<string name="keyboard_shortcut_group_system">System</string>
<!-- User visible title for the keyboard shortcut that takes the user to the home screen. -->
<string name="keyboard_shortcut_group_system_home">Home</string>
- <!-- User visible title for the the keyboard shortcut that takes the user to the recents screen. -->
+ <!-- User visible title for the keyboard shortcut that takes the user to the recents screen. -->
<string name="keyboard_shortcut_group_system_recents">Recents</string>
- <!-- User visible title for the the keyboard shortcut that triggers the back action. -->
+ <!-- User visible title for the keyboard shortcut that triggers the back action. -->
<string name="keyboard_shortcut_group_system_back">Back</string>
- <!-- User visible title for the the keyboard shortcut that triggers the notification shade. -->
+ <!-- User visible title for the keyboard shortcut that triggers the notification shade. -->
<string name="keyboard_shortcut_group_system_notifications">Notifications</string>
- <!-- User visible title for the the keyboard shortcut that triggers the keyboard shortcuts helper. -->
+ <!-- User visible title for the keyboard shortcut that triggers the keyboard shortcuts helper. -->
<string name="keyboard_shortcut_group_system_shortcuts_helper">Keyboard Shortcuts</string>
- <!-- User visible title for the the keyboard shortcut that switches to the next hardware keyboard layout. [CHAR LIMIT=30] -->
+ <!-- User visible title for the keyboard shortcut that switches to the next hardware keyboard layout. -->
<string name="keyboard_shortcut_group_system_switch_input">Switch keyboard layout</string>
- <!-- User visible title for the system-wide applications keyboard shortcuts list. -->
+ <!-- Content description for the clear text button in shortcut search list. [CHAR LIMIT=NONE] -->
+ <string name="keyboard_shortcut_clear_text">Clear text</string>
+ <!-- The title for keyboard shortcut search list [CHAR LIMIT=25] -->
+ <string name="keyboard_shortcut_search_list_title">Shortcuts</string>
+ <!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] -->
+ <string name="keyboard_shortcut_search_list_hint">Search shortcuts</string>
+ <!-- The description for no shortcuts results [CHAR LIMIT=25] -->
+ <string name="keyboard_shortcut_search_list_no_result">No shortcuts found</string>
+ <!-- The title of system category in shortcut search list. [CHAR LIMIT=15] -->
+ <string name="keyboard_shortcut_search_category_system">System</string>
+ <!-- The title of input category in shortcut search list. [CHAR LIMIT=15] -->
+ <string name="keyboard_shortcut_search_category_input">Input</string>
+ <!-- The title of open apps category in shortcut search list. [CHAR LIMIT=15] -->
+ <string name="keyboard_shortcut_search_category_open_apps">Open apps</string>
+ <!-- The title of current app category in shortcut search list. [CHAR LIMIT=15] -->
+ <string name="keyboard_shortcut_search_category_current_app">Current app</string>
+
+ <!-- User visible title for the keyboard shortcut that triggers the notification shade. [CHAR LIMIT=70] -->
+ <string name="group_system_access_notification_shade">Access notification shade</string>
+ <!-- User visible title for the keyboard shortcut that takes a full screenshot. [CHAR LIMIT=70] -->
+ <string name="group_system_full_screenshot">Take a full screenshot</string>
+ <!-- User visible title for the keyboard shortcut that access list of system / apps shortcuts. [CHAR LIMIT=70] -->
+ <string name="group_system_access_system_app_shortcuts">Access list of system / apps shortcuts</string>
+ <!-- User visible title for the keyboard shortcut that goes back to previous state. [CHAR LIMIT=70] -->
+ <string name="group_system_go_back">Back: go back to previous state (back button)</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the home screen. [CHAR LIMIT=70] -->
+ <string name="group_system_access_home_screen">Access home screen</string>
+ <!-- User visible title for the keyboard shortcut that triggers overview of open apps. [CHAR LIMIT=70] -->
+ <string name="group_system_overview_open_apps">Overview of open apps</string>
+ <!-- User visible title for the keyboard shortcut that cycles through recent apps (forward). [CHAR LIMIT=70] -->
+ <string name="group_system_cycle_forward">Cycle through recent apps (forward)</string>
+ <!-- User visible title for the keyboard shortcut that cycles through recent apps (back). [CHAR LIMIT=70] -->
+ <string name="group_system_cycle_back">Cycle through recent apps (back)</string>
+ <!-- User visible title for the keyboard shortcut that accesses list of all apps and search. [CHAR LIMIT=70] -->
+ <string name="group_system_access_all_apps_search">Access list of all apps and search (i.e. Search/Launcher)</string>
+ <!-- User visible title for the keyboard shortcut that hides and (re)showes taskbar. [CHAR LIMIT=70] -->
+ <string name="group_system_hide_reshow_taskbar">Hide and (re)show taskbar</string>
+ <!-- User visible title for the keyboard shortcut that accesses system settings. [CHAR LIMIT=70] -->
+ <string name="group_system_access_system_settings">Access system settings</string>
+ <!-- User visible title for the keyboard shortcut that accesses Google Assistant. [CHAR LIMIT=70] -->
+ <string name="group_system_access_google_assistant">Access Google Assistant</string>
+ <!-- User visible title for the keyboard shortcut that locks screen. [CHAR LIMIT=70] -->
+ <string name="group_system_lock_screen">Lock screen</string>
+ <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
+ <string name="group_system_quick_memo">Pull up Notes app for quick memo</string>
+
+ <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string>
+ <!-- User visible title for the keyboard shortcut that enters split screen with current app to RHS [CHAR LIMIT=70] -->
+ <string name="system_multitasking_rhs">Enter Split screen with current app to RHS</string>
+ <!-- User visible title for the keyboard shortcut that enters split screen with current app to LHS [CHAR LIMIT=70] -->
+ <string name="system_multitasking_lhs">Enter Split screen with current app to LHS</string>
+ <!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
+ <string name="system_multitasking_full_screen">Switch from Split screen to full screen</string>
+ <!-- User visible title for the keyboard shortcut that replaces an app from one to another during split screen [CHAR LIMIT=70] -->
+ <string name="system_multitasking_replace">During Split screen: replace an app from one to another</string>
+
+ <!-- User visible title for the input keyboard shortcuts list. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_input">Input</string>
+ <!-- User visible title for the keyboard shortcut that switches input language (next language). [CHAR LIMIT=70] -->
+ <string name="input_switch_input_language_next">Switch input language (next language)</string>
+ <!-- User visible title for the keyboard shortcut that switches input language (previous language). [CHAR LIMIT=70] -->
+ <string name="input_switch_input_language_previous">Switch input language (previous language)</string>
+ <!-- User visible title for the keyboard shortcut that accesses emoji. [CHAR LIMIT=70] -->
+ <string name="input_access_emoji">Access emoji</string>
+ <!-- User visible title for the keyboard shortcut that accesses voice typing. [CHAR LIMIT=70] -->
+ <string name="input_access_voice_typing">Access voice typing</string>
+
+ <!-- User visible title for the system-wide applications keyboard shortcuts list. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications">Applications</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the assist app. -->
+ <!-- User visible title for the keyboard shortcut that takes the user to the assist app. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications_assist">Assist</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the browser app. -->
- <string name="keyboard_shortcut_group_applications_browser">Browser</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. -->
+ <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_browser">Browser (Chrome as default)</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications_contacts">Contacts</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the email app. -->
- <string name="keyboard_shortcut_group_applications_email">Email</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. -->
+ <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_email">Email (Gmail as default)</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications_sms">SMS</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the music app. -->
+ <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications_music">Music</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the YouTube app. -->
- <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. -->
+ <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications_calendar">Calendar</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calculator app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calculator">Calculator</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_maps">Maps</string>
<!-- SysUI Tuner: Label for screen about do not disturb settings [CHAR LIMIT=60] -->
<string name="volume_and_do_not_disturb">Do Not Disturb</string>
@@ -2233,6 +2304,14 @@
<!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
<string name="inattentive_sleep_warning_title">Standby</string>
+ <!-- Font scaling -->
+ <!-- Font scaling: Quick Settings dialog title [CHAR LIMIT=30] -->
+ <string name="font_scaling_dialog_title">Font Size</string>
+ <!-- Content Description for the icon button to make fonts smaller. [CHAR LIMIT=30] -->
+ <string name="font_scaling_smaller">Make smaller</string>
+ <!-- Content Description for the icon button to make fonts larger. [CHAR LIMIT=30] -->
+ <string name="font_scaling_larger">Make larger</string>
+
<!-- Window Magnification strings -->
<!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
<string name="magnification_window_title">Magnification Window</string>
@@ -2950,4 +3029,22 @@
[CHAR LIMIT=32]
-->
<string name="lock_screen_settings">Lock screen settings</string>
+
+ <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]-->
+ <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string>
+
+ <!-- Content description for camera blocked icon on dream [CHAR LIMIT=NONE] -->
+ <string name="camera_blocked_dream_overlay_content_description">Camera blocked</string>
+
+ <!-- Content description for camera and microphone blocked icon on dream [CHAR LIMIT=NONE] -->
+ <string name="camera_and_microphone_blocked_dream_overlay_content_description">Camera and microphone blocked</string>
+
+ <!-- Content description for camera and microphone disabled icon on dream [CHAR LIMIT=NONE] -->
+ <string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string>
+
+ <!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] -->
+ <string name="priority_mode_dream_overlay_content_description">Priority mode on</string>
+
+ <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] -->
+ <string name="assistant_attention_content_description">Assistant attention on</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9398c89..2cd2173 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1375,4 +1375,23 @@
<item name="android:lineHeight">@dimen/magnification_setting_button_line_height</item>
<item name="android:textAlignment">center</item>
</style>
+
+ <style name="ShortCutButton" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/shortcut_button_colored</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:padding">4dp</item>
+ <item name="android:textColor">?androidprv:attr/textColorSecondary</item>
+ </style>
+
+ <style name="ShortcutHorizontalDivider">
+ <item name="android:layout_width">120dp</item>
+ <item name="android:layout_height">1dp</item>
+ <item name="android:layout_gravity">center_horizontal</item>
+ <item name="android:background">?android:attr/dividerHorizontal</item>
+ </style>
+
+ <style name="ShortcutItemBackground">
+ <item name="android:background">@color/ksh_key_item_new_background</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
index 30156a0..5e71bbe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
@@ -75,7 +75,7 @@
DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
mSurfaceControlViewHost = new SurfaceControlViewHost(context,
dm.getDisplay(SurfaceViewRequestUtils.getDisplayId(bundle)),
- windowlessWindowManager);
+ windowlessWindowManager, "SurfaceViewRequestReceiver");
WindowManager.LayoutParams layoutParams =
new WindowManager.LayoutParams(
viewSize.getWidth(),
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 54ae84f9..ead1a10 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -303,9 +303,18 @@
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
- pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=${
- onUnlockIntentWhenBiometricEnrolled.map { BiometricType.values()[it] }
- }")
+
+ val onUnlockIntentWhenBiometricEnrolledString =
+ onUnlockIntentWhenBiometricEnrolled.map {
+ for (biometricType in BiometricType.values()) {
+ if (biometricType.intValue == it) {
+ return@map biometricType.name
+ }
+ }
+ return@map "UNKNOWN"
+ }
+ pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
+ "$onUnlockIntentWhenBiometricEnrolledString")
pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
"$faceAcquireInfoToTriggerBiometricFailOn")
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 1051de3..6139403 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -202,11 +202,8 @@
mKeyguardSecurityContainerController.onPause();
}
- /**
- * Reinflate the view flipper child view.
- */
- public void reinflateViewFlipper() {
- mKeyguardSecurityContainerController.reinflateViewFlipper();
+ public void resetSecurityContainer() {
+ mKeyguardSecurityContainerController.reset();
}
/**
@@ -235,19 +232,23 @@
/**
* Starts the animation when the Keyguard gets shown.
*/
- public void appear() {
+ public void appear(int statusBarHeight) {
// We might still be collapsed and the view didn't have time to layout yet or still
// be small, let's wait on the predraw to do the animation in that case.
- mView.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mView.getViewTreeObserver().removeOnPreDrawListener(this);
- mKeyguardSecurityContainerController.startAppearAnimation();
- return true;
- }
- });
- mView.requestLayout();
+ if (mView.getHeight() != 0 && mView.getHeight() != statusBarHeight) {
+ mKeyguardSecurityContainerController.startAppearAnimation();
+ } else {
+ mView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mKeyguardSecurityContainerController.startAppearAnimation();
+ return true;
+ }
+ });
+ mView.requestLayout();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 8281c8b..20baa81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -744,20 +744,17 @@
}
private void reloadColors() {
- reinflateViewFlipper();
+ resetViewFlipper();
mView.reloadColors();
}
/** Handles density or font scale changes. */
private void onDensityOrFontScaleChanged() {
- reinflateViewFlipper();
+ resetViewFlipper();
mView.onDensityOrFontScaleChanged();
}
- /**
- * Reinflate the view flipper child view.
- */
- public void reinflateViewFlipper() {
+ private void resetViewFlipper() {
mSecurityViewFlipperController.clearViews();
mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
mKeyguardSecurityCallback);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9f1c382..5b628f8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -102,6 +102,7 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.usb.UsbManager;
import android.nfc.NfcAdapter;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -138,6 +139,7 @@
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
@@ -329,6 +331,8 @@
// Battery status
@VisibleForTesting
BatteryStatus mBatteryStatus;
+ @VisibleForTesting
+ boolean mIncompatibleCharger;
private StrongAuthTracker mStrongAuthTracker;
@@ -1572,10 +1576,20 @@
MSG_TIMEZONE_UPDATE, intent.getStringExtra(Intent.EXTRA_TIMEZONE));
mHandler.sendMessage(msg);
} else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-
+ // Clear incompatible charger state when device is unplugged.
+ if (!BatteryStatus.isPluggedIn(intent)) {
+ mIncompatibleCharger = false;
+ }
final Message msg = mHandler.obtainMessage(
- MSG_BATTERY_UPDATE, new BatteryStatus(intent));
+ MSG_BATTERY_UPDATE, new BatteryStatus(intent, mIncompatibleCharger));
mHandler.sendMessage(msg);
+ } else if (UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED.equals(action)) {
+ mIncompatibleCharger = Utils.containsIncompatibleChargers(context, TAG);
+ BatteryStatus batteryStatus = BatteryStatus.create(context, mIncompatibleCharger);
+ if (batteryStatus != null) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_BATTERY_UPDATE, batteryStatus));
+ }
} else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) {
SimData args = SimData.fromIntent(intent);
// ACTION_SIM_STATE_CHANGED is rebroadcast after unlocking the device to
@@ -2251,6 +2265,7 @@
filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
// Since ACTION_SERVICE_STATE is being moved to a non-sticky broadcast, trigger the
// listener now with the service state from the default sub.
@@ -3527,8 +3542,6 @@
final boolean wasPluggedIn = old.isPluggedIn();
final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
&& (old.status != current.status);
- final boolean nowPresent = current.present;
- final boolean wasPresent = old.present;
// change in plug state is always interesting
if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
@@ -3545,8 +3558,13 @@
return true;
}
- // Battery either showed up or disappeared
- if (wasPresent != nowPresent) {
+ // change in battery is present or not
+ if (old.present != current.present) {
+ return true;
+ }
+
+ // change in the incompatible charger
+ if (!old.incompatibleCharger.equals(current.incompatibleCharger)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 5135eed..b30a0e0 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -51,6 +51,7 @@
private float mStartRadius;
private float mEndRadius;
private int mHeight;
+ private boolean mInitialized;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -97,6 +98,11 @@
mEndRadius = height / 4f;
mExpandAnimator.setFloatValues(mStartRadius, mEndRadius);
mContractAnimator.setFloatValues(mEndRadius, mStartRadius);
+ // Set initial corner radius.
+ if (!mInitialized) {
+ mBackground.setCornerRadius(mStartRadius);
+ mInitialized = true;
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
new file mode 100644
index 0000000..54f933a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.accessibility.fontscaling
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.os.Bundle
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.widget.Button
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SystemSettings
+
+/** The Dialog that contains a seekbar for changing the font size. */
+class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
+ SystemUIDialog(context) {
+ private val strEntryValues: Array<String> =
+ context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
+ private lateinit var title: TextView
+ private lateinit var doneButton: Button
+ private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
+
+ private val configuration: Configuration =
+ Configuration(context.getResources().getConfiguration())
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTitle(R.string.font_scaling_dialog_title)
+ setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null))
+ setPositiveButton(
+ R.string.quick_settings_done,
+ /* onClick = */ null,
+ /* dismissOnClick = */ true
+ )
+ super.onCreate(savedInstanceState)
+
+ title = requireViewById(com.android.internal.R.id.alertTitle)
+ doneButton = requireViewById(com.android.internal.R.id.button1)
+ seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider)
+
+ seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
+
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
+ seekBarWithIconButtonsView.setProgress(fontSizeValueToIndex(currentScale))
+
+ seekBarWithIconButtonsView.setOnSeekBarChangeListener(
+ object : OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[progress])
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {
+ // Do nothing
+ }
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ // Do nothing
+ }
+ }
+ )
+ doneButton.setOnClickListener { dismiss() }
+ }
+
+ private fun fontSizeValueToIndex(value: Float): Int {
+ var lastValue = strEntryValues[0].toFloat()
+ for (i in 1 until strEntryValues.size) {
+ val thisValue = strEntryValues[i].toFloat()
+ if (value < lastValue + (thisValue - lastValue) * .5f) {
+ return i - 1
+ }
+ lastValue = thisValue
+ }
+ return strEntryValues.size - 1
+ }
+
+ override fun onConfigurationChanged(configuration: Configuration) {
+ super.onConfigurationChanged(configuration)
+
+ val configDiff = configuration.diff(this.configuration)
+ this.configuration.setTo(configuration)
+
+ if (configDiff and ActivityInfo.CONFIG_FONT_SCALE != 0) {
+ title.post {
+ title.setTextAppearance(R.style.TextAppearance_Dialog_Title)
+ doneButton.setTextAppearance(R.style.Widget_Dialog_Button)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index ae6a08f..a2077a3 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -22,6 +22,7 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
@@ -37,6 +38,8 @@
private static final int DEFAULT_SEEKBAR_MAX = 6;
private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+ private ViewGroup mIconStartFrame;
+ private ViewGroup mIconEndFrame;
private ImageView mIconStart;
private ImageView mIconEnd;
private SeekBar mSeekbar;
@@ -62,6 +65,8 @@
LayoutInflater.from(context).inflate(
R.layout.seekbar_with_icon_buttons, this, /* attachToRoot= */ true);
+ mIconStartFrame = findViewById(R.id.icon_start_frame);
+ mIconEndFrame = findViewById(R.id.icon_end_frame);
mIconStart = findViewById(R.id.icon_start);
mIconEnd = findViewById(R.id.icon_end);
mSeekbar = findViewById(R.id.seekbar);
@@ -80,24 +85,22 @@
mSeekbar.setMax(max);
setProgress(progress);
- int iconStartContentDescriptionId = typedArray.getResourceId(
+ int iconStartFrameContentDescriptionId = typedArray.getResourceId(
R.styleable.SeekBarWithIconButtonsView_Layout_iconStartContentDescription,
/* defValue= */ 0);
- int iconEndContentDescriptionId = typedArray.getResourceId(
+ int iconEndFrameContentDescriptionId = typedArray.getResourceId(
R.styleable.SeekBarWithIconButtonsView_Layout_iconEndContentDescription,
/* defValue= */ 0);
- if (iconStartContentDescriptionId != 0) {
+ if (iconStartFrameContentDescriptionId != 0) {
final String contentDescription =
- context.getString(iconStartContentDescriptionId);
- mIconStart.setContentDescription(contentDescription);
+ context.getString(iconStartFrameContentDescriptionId);
+ mIconStartFrame.setContentDescription(contentDescription);
}
- if (iconEndContentDescriptionId != 0) {
+ if (iconEndFrameContentDescriptionId != 0) {
final String contentDescription =
- context.getString(iconEndContentDescriptionId);
- mIconEnd.setContentDescription(contentDescription);
+ context.getString(iconEndFrameContentDescriptionId);
+ mIconEndFrame.setContentDescription(contentDescription);
}
-
- typedArray.recycle();
} else {
mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
setProgress(DEFAULT_SEEKBAR_PROGRESS);
@@ -109,7 +112,7 @@
final int progress = mSeekbar.getProgress();
if (progress > 0) {
mSeekbar.setProgress(progress - 1);
- setIconViewEnabled(mIconStart, mSeekbar.getProgress() > 0);
+ setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
}
});
@@ -117,13 +120,15 @@
final int progress = mSeekbar.getProgress();
if (progress < mSeekbar.getMax()) {
mSeekbar.setProgress(progress + 1);
- setIconViewEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
+ setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
}
});
}
- private static void setIconViewEnabled(View iconView, boolean enabled) {
+ private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
iconView.setEnabled(enabled);
+ final ViewGroup iconFrame = (ViewGroup) iconView.getParent();
+ iconFrame.setEnabled(enabled);
}
/**
@@ -141,12 +146,22 @@
* Icon End will need to be enabled when the seekbar progress is less than Max.
*/
private void updateIconViewIfNeeded(int progress) {
- setIconViewEnabled(mIconStart, progress > 0);
- setIconViewEnabled(mIconEnd, progress < mSeekbar.getMax());
+ setIconViewAndFrameEnabled(mIconStart, progress > 0);
+ setIconViewAndFrameEnabled(mIconEnd, progress < mSeekbar.getMax());
+ }
+
+ /**
+ * Sets max to the seekbar in the layout.
+ */
+ public void setMax(int max) {
+ mSeekbar.setMax(max);
}
/**
* Sets progress to the seekbar in the layout.
+ * If the progress is smaller than or equals to 0, the IconStart will be disabled. If the
+ * progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress
+ * will be constrained in {@link SeekBar}.
*/
public void setProgress(int progress) {
mSeekbar.setProgress(progress);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index d0c5007..fadabb4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.KeyboardShortcutsModule;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -100,7 +101,8 @@
QSModule.class,
ReferenceScreenshotModule.class,
StartCentralSurfacesModule.class,
- VolumeModule.class
+ VolumeModule.class,
+ KeyboardShortcutsModule.class
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cbeff2b..cd5f149 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -70,6 +70,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -266,6 +267,7 @@
NotifPipeline notifPipeline,
SysUiState sysUiState,
FeatureFlags featureFlags,
+ NotifPipelineFlags notifPipelineFlags,
@Main Executor sysuiMainExecutor) {
return Optional.ofNullable(BubblesManager.create(context,
bubblesOptional,
@@ -283,6 +285,7 @@
notifPipeline,
sysUiState,
featureFlags,
+ notifPipelineFlags,
sysuiMainExecutor));
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 27641fe..a14aa52 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -269,6 +269,8 @@
*/
private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
mWindow = new PhoneWindow(mContext);
+ // Default to SystemUI name for TalkBack.
+ mWindow.setTitle("");
mWindow.setAttributes(layoutParams);
mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 2221a04..74a49a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -259,7 +259,8 @@
mConnectivityManager.getActiveNetwork());
final boolean available = capabilities != null
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
- showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available);
+ showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available,
+ R.string.wifi_unavailable_dream_overlay_content_description);
}
private void updateAlarmStatusIcon() {
@@ -274,7 +275,8 @@
private void updateAssistantAttentionIcon() {
showIcon(DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
- mDreamOverlayStateController.hasAssistantAttention());
+ mDreamOverlayStateController.hasAssistantAttention(),
+ R.string.assistant_attention_content_description);
}
private void updateVisibility() {
@@ -304,13 +306,16 @@
@DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED,
- !micBlocked && cameraBlocked);
+ !micBlocked && cameraBlocked,
+ R.string.camera_blocked_dream_overlay_content_description);
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED,
- micBlocked && !cameraBlocked);
+ micBlocked && !cameraBlocked,
+ R.string.microphone_blocked_dream_overlay_content_description);
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
- micBlocked && cameraBlocked);
+ micBlocked && cameraBlocked,
+ R.string.camera_and_microphone_blocked_dream_overlay_content_description);
}
private String buildNotificationsContentDescription(int notificationCount) {
@@ -323,11 +328,13 @@
private void updatePriorityModeStatusIcon() {
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON,
- mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF);
+ mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF,
+ R.string.priority_mode_dream_overlay_content_description);
}
- private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show) {
- showIcon(iconType, show, null);
+ private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show,
+ int contentDescriptionResId) {
+ showIcon(iconType, show, mResources.getString(contentDescriptionResId));
}
private void showIcon(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d5634e3..85e050c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -514,9 +514,7 @@
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips")
// TODO(b/268484562): Tracking bug
- @JvmField
- val SCREENSHOT_METADATA_REFACTOR =
- unreleasedFlag(1305, "screenshot_metadata_refactor", teamfood = true)
+ @JvmField val SCREENSHOT_METADATA_REFACTOR = releasedFlag(1305, "screenshot_metadata_refactor")
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
@@ -568,6 +566,12 @@
val CONTROLS_MANAGEMENT_NEW_FLOWS =
releasedFlag(2002, "controls_management_new_flows", teamfood = true)
+ // Enables removing app from Home control panel as a part of a new flow
+ // TODO(b/269132640): Tracking Bug
+ @JvmField
+ val APP_PANELS_REMOVE_APPS_ALLOWED =
+ unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = false)
+
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 5a9f775..c9f645d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
+import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.pm.PackageManager
import com.android.systemui.R
@@ -27,10 +28,14 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.withContext
@SysUISingleton
class CameraQuickAffordanceConfig
@@ -39,6 +44,9 @@
@Application private val context: Context,
private val packageManager: PackageManager,
private val cameraGestureHelper: Lazy<CameraGestureHelper>,
+ private val userTracker: UserTracker,
+ private val devicePolicyManager: DevicePolicyManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
override val key: String
@@ -79,7 +87,12 @@
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
- private fun isLaunchable(): Boolean {
- return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
+ private suspend fun isLaunchable(): Boolean {
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) &&
+ withContext(backgroundDispatcher) {
+ !devicePolicyManager.getCameraDisabled(null, userTracker.userId) &&
+ devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) and
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA == 0
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
index d9ec3b1..6f821a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
+import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import com.android.systemui.ActivityIntentHelper
@@ -29,10 +30,13 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.withContext
@SysUISingleton
class VideoCameraQuickAffordanceConfig
@@ -42,6 +46,8 @@
private val cameraIntents: CameraIntentsWrapper,
private val activityIntentHelper: ActivityIntentHelper,
private val userTracker: UserTracker,
+ private val devicePolicyManager: DevicePolicyManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
private val intent: Intent by lazy {
@@ -63,8 +69,8 @@
get() = R.drawable.ic_videocam
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
- get() =
- flowOf(
+ get() = flow {
+ emit(
if (isLaunchable()) {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
@@ -77,6 +83,7 @@
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
)
+ }
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
return if (isLaunchable()) {
@@ -95,11 +102,14 @@
)
}
- private fun isLaunchable(): Boolean {
+ private suspend fun isLaunchable(): Boolean {
return activityIntentHelper.getTargetActivityInfo(
intent,
userTracker.userId,
true,
- ) != null
+ ) != null &&
+ withContext(backgroundDispatcher) {
+ !devicePolicyManager.getCameraDisabled(null, userTracker.userId)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index bb0d260..9f09d53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -22,6 +22,7 @@
import android.window.OnBackAnimationCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.KeyguardHostViewController
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -97,14 +98,14 @@
viewModel.setBouncerViewDelegate(delegate)
launch {
viewModel.show.collect {
- // Reset Security Container entirely.
- hostViewController.reinflateViewFlipper()
hostViewController.showPromptReason(it.promptReason)
it.errorMessage?.let { errorMessage ->
hostViewController.showErrorMessage(errorMessage)
}
hostViewController.showPrimarySecurityScreen()
- hostViewController.appear()
+ hostViewController.appear(
+ SystemBarUtils.getStatusBarHeight(view.context)
+ )
hostViewController.onResume()
}
}
@@ -113,6 +114,7 @@
viewModel.hide.collect {
hostViewController.cancelDismissAction()
hostViewController.cleanUp()
+ hostViewController.resetSecurityContainer()
}
}
@@ -158,6 +160,15 @@
}
launch {
+ viewModel.isBouncerVisible
+ .filter { !it }
+ .collect {
+ // Remove existing input for security reasons.
+ hostViewController.resetSecurityContainer()
+ }
+ }
+
+ launch {
viewModel.keyguardPosition.collect { position ->
hostViewController.updateKeyguardPosition(position)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 403576c..d977f91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -97,6 +97,7 @@
context,
displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)),
hostToken,
+ "KeyguardPreviewRenderer"
)
disposables.add(DisposableHandle { host.release() })
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index b72923a..68d2c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -164,13 +164,13 @@
mediaCarouselScrollHandler.scrollToStart()
}
}
- private var currentlyExpanded = true
+
+ @VisibleForTesting
+ var currentlyExpanded = true
set(value) {
if (field != value) {
field = value
- for (player in MediaPlayerData.players()) {
- player.setListening(field)
- }
+ updateSeekbarListening(mediaCarouselScrollHandler.visibleToUser)
}
}
@@ -259,6 +259,7 @@
executor,
this::onSwipeToDismiss,
this::updatePageIndicatorLocation,
+ this::updateSeekbarListening,
this::closeGuts,
falsingCollector,
falsingManager,
@@ -590,6 +591,17 @@
?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
}
}
+ // Check postcondition: mediaContent should have the same number of children as there
+ // are
+ // elements in mediaPlayers.
+ if (MediaPlayerData.players().size != mediaContent.childCount) {
+ Log.e(
+ TAG,
+ "Size of players list and number of views in carousel are out of sync. " +
+ "Players size is ${MediaPlayerData.players().size}. " +
+ "View count is ${mediaContent.childCount}."
+ )
+ }
}
// Returns true if new player is added
@@ -618,7 +630,9 @@
)
newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
newPlayer.bindPlayer(data, key)
- newPlayer.setListening(currentlyExpanded)
+ newPlayer.setListening(
+ mediaCarouselScrollHandler.visibleToUser && currentlyExpanded
+ )
MediaPlayerData.addMediaPlayer(
key,
data,
@@ -665,17 +679,6 @@
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
mediaFrame.requiresRemeasuring = true
- // Check postcondition: mediaContent should have the same number of children as there
- // are
- // elements in mediaPlayers.
- if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.e(
- TAG,
- "Size of players list and number of views in carousel are out of sync. " +
- "Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}."
- )
- }
return existingPlayer == null
}
@@ -914,6 +917,13 @@
.toFloat()
}
+ /** Update listening to seekbar. */
+ private fun updateSeekbarListening(visibleToUser: Boolean) {
+ for (player in MediaPlayerData.players()) {
+ player.setListening(visibleToUser && currentlyExpanded)
+ }
+ }
+
/** Update the dimension of this carousel. */
private fun updateCarouselDimensions() {
var width = 0
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 36b2eda..1ace316 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -57,6 +57,7 @@
private val mainExecutor: DelayableExecutor,
val dismissCallback: () -> Unit,
private var translationChangedListener: () -> Unit,
+ private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit,
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
@@ -177,6 +178,12 @@
/** Whether the media card is visible to user if any */
var visibleToUser: Boolean = false
+ set(value) {
+ if (field != value) {
+ field = value
+ seekBarUpdateListener.invoke(field)
+ }
+ }
/** Whether the quick setting is expanded or not */
var qsExpanded: Boolean = false
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 6076e58..288266a 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
@@ -347,6 +347,11 @@
mSeekBarViewModel.setListening(listening);
}
+ @VisibleForTesting
+ public boolean getListening() {
+ return mSeekBarViewModel.getListening();
+ }
+
/** Sets whether the user is touching the seek bar to change the track position. */
private void setIsScrubbing(boolean isScrubbing) {
if (mMediaData == null || mMediaData.getSemanticActions() == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 4d8f89e..b9cd535 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -19,8 +19,12 @@
import android.os.Handler
import android.os.Looper
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.systemui.R
+import com.android.systemui.accessibility.fontscaling.FontScalingDialog
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -30,6 +34,8 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
class FontScalingTile
@@ -42,7 +48,9 @@
metricsLogger: MetricsLogger,
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
- qsLogger: QSLogger
+ qsLogger: QSLogger,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val systemSettings: SystemSettings
) :
QSTileImpl<QSTile.State?>(
host,
@@ -54,7 +62,7 @@
activityStarter,
qsLogger
) {
- private val mIcon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
+ private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
override fun isAvailable(): Boolean {
return false
@@ -66,11 +74,24 @@
return state
}
- override fun handleClick(view: View?) {}
+ override fun handleClick(view: View?) {
+ mUiHandler.post {
+ val dialog: SystemUIDialog = FontScalingDialog(mContext, systemSettings)
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ view,
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
+ )
+ } else {
+ dialog.show()
+ }
+ }
+ }
override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
state?.label = mContext.getString(R.string.quick_settings_font_scaling_label)
- state?.icon = mIcon
+ state?.icon = icon
}
override fun getLongClickIntent(): Intent? {
@@ -80,4 +101,8 @@
override fun getTileLabel(): CharSequence {
return mContext.getString(R.string.quick_settings_font_scaling_label)
}
+
+ companion object {
+ private const val INTERACTION_JANK_TAG = "font_scaling"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 7a62bae..fc94aed 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -93,7 +93,13 @@
@UiEvent(doc = "User has discarded the result of a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
@UiEvent(doc = "A screenshot has been taken and saved to work profile")
- SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
+ SCREENSHOT_SAVED_TO_WORK_PROFILE(1240),
+ @UiEvent(doc = "Notes application triggered the screenshot for notes")
+ SCREENSHOT_FOR_NOTE_TRIGGERED(1308),
+ @UiEvent(doc = "User accepted the screenshot to be sent to the notes app")
+ SCREENSHOT_FOR_NOTE_ACCEPTED(1309),
+ @UiEvent(doc = "User cancelled the screenshot for notes app flow")
+ SCREENSHOT_FOR_NOTE_CANCELLED(1310);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index f8d86a0..3133924 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -17,15 +17,21 @@
package com.android.systemui.screenshot;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
@@ -33,15 +39,20 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import androidx.activity.ComponentActivity;
+import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLogger.UiEventEnum;
import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
@@ -64,9 +75,15 @@
*
* TODO(b/267309532): Polish UI and animations.
*/
-public final class AppClipsActivity extends ComponentActivity {
+public class AppClipsActivity extends ComponentActivity {
+
+ private static final String TAG = AppClipsActivity.class.getSimpleName();
+ private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
private final AppClipsViewModel.Factory mViewModelFactory;
+ private final PackageManager mPackageManager;
+ private final UserTracker mUserTracker;
+ private final UiEventLogger mUiEventLogger;
private final BroadcastReceiver mBroadcastReceiver;
private final IntentFilter mIntentFilter;
@@ -80,10 +97,17 @@
private AppClipsViewModel mViewModel;
private ResultReceiver mResultReceiver;
+ @Nullable
+ private String mCallingPackageName;
+ private int mCallingPackageUid;
@Inject
- public AppClipsActivity(AppClipsViewModel.Factory viewModelFactory) {
+ public AppClipsActivity(AppClipsViewModel.Factory viewModelFactory,
+ PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger) {
mViewModelFactory = viewModelFactory;
+ mPackageManager = packageManager;
+ mUserTracker = userTracker;
+ mUiEventLogger = uiEventLogger;
mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -113,6 +137,7 @@
RECEIVER_NOT_EXPORTED);
Intent intent = getIntent();
+ setUpUiLogging(intent);
mResultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER, ResultReceiver.class);
if (mResultReceiver == null) {
setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
@@ -169,6 +194,17 @@
}
}
+ private void setUpUiLogging(Intent intent) {
+ mCallingPackageName = intent.getStringExtra(EXTRA_CALLING_PACKAGE_NAME);
+ mCallingPackageUid = 0;
+ try {
+ mCallingPackageUid = mPackageManager.getApplicationInfoAsUser(mCallingPackageName,
+ APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid;
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Couldn't find notes app UID " + e);
+ }
+ }
+
private void setScreenshot(Bitmap screenshot) {
// Set background, status and navigation bar colors as the activity is no longer
// translucent.
@@ -228,6 +264,7 @@
data.putParcelable(EXTRA_SCREENSHOT_URI, uri);
try {
mResultReceiver.send(Activity.RESULT_OK, data);
+ logUiEvent(SCREENSHOT_FOR_NOTE_ACCEPTED);
} catch (Exception e) {
// Do nothing.
}
@@ -251,6 +288,9 @@
data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode);
try {
mResultReceiver.send(RESULT_OK, data);
+ if (errorCode == Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED) {
+ logUiEvent(SCREENSHOT_FOR_NOTE_CANCELLED);
+ }
} catch (Exception e) {
// Do nothing.
}
@@ -259,6 +299,10 @@
mResultReceiver = null;
}
+ private void logUiEvent(UiEventEnum uiEvent) {
+ mUiEventLogger.log(uiEvent, mCallingPackageUid, mCallingPackageName);
+ }
+
private void updateImageDimensions() {
Drawable drawable = mPreview.getDrawable();
if (drawable == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 4759cc6..1946b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -24,26 +24,33 @@
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.ResultReceiver;
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.notetask.NoteTaskController;
+import com.android.systemui.settings.UserTracker;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
@@ -70,15 +77,23 @@
public class AppClipsTrampolineActivity extends Activity {
private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
- public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
- public static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
- static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+ static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
+ private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
private final DevicePolicyManager mDevicePolicyManager;
private final FeatureFlags mFeatureFlags;
private final Optional<Bubbles> mOptionalBubbles;
private final NoteTaskController mNoteTaskController;
+ private final PackageManager mPackageManager;
+ private final UserTracker mUserTracker;
+ private final UiEventLogger mUiEventLogger;
private final ResultReceiver mResultReceiver;
private Intent mKillAppClipsBroadcastIntent;
@@ -86,11 +101,15 @@
@Inject
public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
+ PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
@Main Handler mainHandler) {
mDevicePolicyManager = devicePolicyManager;
mFeatureFlags = flags;
mOptionalBubbles = optionalBubbles;
mNoteTaskController = noteTaskController;
+ mPackageManager = packageManager;
+ mUserTracker = userTracker;
+ mUiEventLogger = uiEventLogger;
mResultReceiver = createResultReceiver(mainHandler);
}
@@ -138,8 +157,12 @@
return;
}
- Intent intent = new Intent().setComponent(componentName).addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK).putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver);
+ String callingPackageName = getCallingPackage();
+ Intent intent = new Intent().setComponent(componentName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
+ .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
+
try {
// Start the App Clips activity.
startActivity(intent);
@@ -150,6 +173,9 @@
new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
.setComponent(componentName)
.setPackage(componentName.getPackageName());
+
+ // Log successful triggering of screenshot for notes.
+ logScreenshotTriggeredUiEvent(callingPackageName);
} catch (ActivityNotFoundException e) {
setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
}
@@ -170,6 +196,18 @@
finish();
}
+ private void logScreenshotTriggeredUiEvent(@Nullable String callingPackageName) {
+ int callingPackageUid = 0;
+ try {
+ callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
+ APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid;
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Couldn't find notes app UID " + e);
+ }
+
+ mUiEventLogger.log(SCREENSHOT_FOR_NOTE_TRIGGERED, callingPackageUid, callingPackageName);
+ }
+
private class AppClipsResultReceiver extends ResultReceiver {
AppClipsResultReceiver(Handler handler) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 5a7b5f9..b2910fd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -18,6 +18,8 @@
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
@@ -29,6 +31,7 @@
import android.os.Process;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@@ -49,7 +52,8 @@
import javax.inject.Inject;
/** A {@link ViewModel} to help with the App Clips screenshot flow. */
-final class AppClipsViewModel extends ViewModel {
+@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+public final class AppClipsViewModel extends ViewModel {
private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
private final ImageExporter mImageExporter;
@@ -76,7 +80,8 @@
}
/** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */
- void performScreenshot() {
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public void performScreenshot() {
mBgExecutor.execute(() -> {
Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot();
mMainExecutor.execute(() -> {
@@ -90,12 +95,14 @@
}
/** Returns a {@link LiveData} that holds the captured screenshot. */
- LiveData<Bitmap> getScreenshot() {
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public LiveData<Bitmap> getScreenshot() {
return mScreenshotLiveData;
}
/** Returns a {@link LiveData} that holds the {@link Uri} where screenshot is saved. */
- LiveData<Uri> getResultLiveData() {
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public LiveData<Uri> getResultLiveData() {
return mResultLiveData;
}
@@ -103,7 +110,8 @@
* Returns a {@link LiveData} that holds the error codes for
* {@link Intent#EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE}.
*/
- LiveData<Integer> getErrorLiveData() {
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public LiveData<Integer> getErrorLiveData() {
return mErrorLiveData;
}
@@ -111,7 +119,8 @@
* Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
* {@link LiveData}.
*/
- void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
mBgExecutor.execute(() -> {
// Render the screenshot bitmap in background.
Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
@@ -151,7 +160,8 @@
}
/** Helper factory to help with injecting {@link AppClipsViewModel}. */
- static final class Factory implements ViewModelProvider.Factory {
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public static final class Factory implements ViewModelProvider.Factory {
private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
private final ImageExporter mImageExporter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
new file mode 100644
index 0000000..01e042b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -0,0 +1,1382 @@
+/*
+ * 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;
+
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.KeyboardShortcutsReceiver;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AssistUtils;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Contains functionality for handling keyboard shortcuts.
+ */
+public final class KeyboardShortcutListSearch {
+ private static final String TAG = KeyboardShortcutListSearch.class.getSimpleName();
+ private static final Object sLock = new Object();
+ @VisibleForTesting static KeyboardShortcutListSearch sInstance;
+
+ private static int SHORTCUT_SYSTEM_INDEX = 0;
+ private static int SHORTCUT_INPUT_INDEX = 1;
+ private static int SHORTCUT_OPENAPPS_INDEX = 2;
+ private static int SHORTCUT_SPECIFICAPP_INDEX = 3;
+
+ private WindowManager mWindowManager;
+ private EditText mSearchEditText;
+ private String mQueryString;
+ private int mCurrentCategoryIndex = 0;
+ private Map<Integer, Boolean> mKeySearchResultMap = new HashMap<>();
+
+ private List<List<KeyboardShortcutMultiMappingGroup>> mFullShortsGroup = new ArrayList<>();
+ private List<KeyboardShortcutMultiMappingGroup> mSpecificAppGroup = new ArrayList<>();
+ private List<KeyboardShortcutMultiMappingGroup> mSystemGroup = new ArrayList<>();
+ private List<KeyboardShortcutMultiMappingGroup> mInputGroup = new ArrayList<>();
+ private List<KeyboardShortcutMultiMappingGroup> mOpenAppsGroup = new ArrayList<>();
+
+ private ArrayList<Button> mFullButtonList = new ArrayList<>();
+ private Button mButtonSystem;
+ private Button mButtonInput;
+ private Button mButtonOpenApps;
+ private Button mButtonSpecificApp;
+ private ImageView mEditTextCancel;
+ private TextView mNoSearchResults;
+
+ private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
+ private final SparseArray<String> mModifierNames = new SparseArray<>();
+ private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
+ private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
+ // Ordered list of modifiers that are supported. All values in this array must exist in
+ // mModifierNames.
+ private final int[] mModifierList = new int[] {
+ KeyEvent.META_META_ON, KeyEvent.META_CTRL_ON, KeyEvent.META_ALT_ON,
+ KeyEvent.META_SHIFT_ON, KeyEvent.META_SYM_ON, KeyEvent.META_FUNCTION_ON
+ };
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @VisibleForTesting Context mContext;
+ private final IPackageManager mPackageManager;
+
+ @VisibleForTesting BottomSheetDialog mKeyboardShortcutsBottomSheetDialog;
+ private KeyCharacterMap mKeyCharacterMap;
+ private KeyCharacterMap mBackupKeyCharacterMap;
+
+ @VisibleForTesting
+ KeyboardShortcutListSearch(Context context, WindowManager windowManager) {
+ this.mContext = new ContextThemeWrapper(
+ context, android.R.style.Theme_DeviceDefault_Settings);
+ this.mPackageManager = AppGlobals.getPackageManager();
+ if (windowManager != null) {
+ this.mWindowManager = windowManager;
+ } else {
+ this.mWindowManager = mContext.getSystemService(WindowManager.class);
+ }
+ loadResources(context);
+ createHardcodedShortcuts();
+ }
+
+ private static KeyboardShortcutListSearch getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new KeyboardShortcutListSearch(context, null);
+ }
+ return sInstance;
+ }
+
+ public static void show(Context context, int deviceId) {
+ MetricsLogger.visible(context,
+ MetricsProto.MetricsEvent.KEYBOARD_SHORTCUTS_HELPER);
+ synchronized (sLock) {
+ if (sInstance != null && !sInstance.mContext.equals(context)) {
+ dismiss();
+ }
+ getInstance(context).showKeyboardShortcuts(deviceId);
+ }
+ }
+
+ public static void toggle(Context context, int deviceId) {
+ synchronized (sLock) {
+ if (isShowing()) {
+ dismiss();
+ } else {
+ show(context, deviceId);
+ }
+ }
+ }
+
+ public static void dismiss() {
+ synchronized (sLock) {
+ if (sInstance != null) {
+ MetricsLogger.hidden(sInstance.mContext,
+ MetricsProto.MetricsEvent.KEYBOARD_SHORTCUTS_HELPER);
+ sInstance.dismissKeyboardShortcuts();
+ sInstance = null;
+ }
+ }
+ }
+
+ private static boolean isShowing() {
+ return sInstance != null && sInstance.mKeyboardShortcutsBottomSheetDialog != null
+ && sInstance.mKeyboardShortcutsBottomSheetDialog.isShowing();
+ }
+
+ private void loadResources(Context context) {
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_PERIOD, ".");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
+ context.getString(R.string.keyboard_key_media_play_pause));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+ context.getString(R.string.keyboard_key_media_previous));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_REWIND,
+ context.getString(R.string.keyboard_key_media_rewind));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+ context.getString(R.string.keyboard_key_media_fast_forward));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_A,
+ context.getString(R.string.keyboard_key_button_template, "A"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_B,
+ context.getString(R.string.keyboard_key_button_template, "B"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_C,
+ context.getString(R.string.keyboard_key_button_template, "C"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_X,
+ context.getString(R.string.keyboard_key_button_template, "X"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Y,
+ context.getString(R.string.keyboard_key_button_template, "Y"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Z,
+ context.getString(R.string.keyboard_key_button_template, "Z"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L1,
+ context.getString(R.string.keyboard_key_button_template, "L1"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R1,
+ context.getString(R.string.keyboard_key_button_template, "R1"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L2,
+ context.getString(R.string.keyboard_key_button_template, "L2"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R2,
+ context.getString(R.string.keyboard_key_button_template, "R2"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_START,
+ context.getString(R.string.keyboard_key_button_template, "Start"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_SELECT,
+ context.getString(R.string.keyboard_key_button_template, "Select"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_MODE,
+ context.getString(R.string.keyboard_key_button_template, "Mode"));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F1, "F1");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F2, "F2");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F3, "F3");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F4, "F4");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F5, "F5");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F6, "F6");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F7, "F7");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F8, "F8");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F9, "F9");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F10, "F10");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F11, "F11");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F12, "F12");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MINUS, "-");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_GRAVE, "~");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_EQUALS, "=");
+
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
+ context.getString(R.string.keyboard_key_numpad_template, "0"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_1,
+ context.getString(R.string.keyboard_key_numpad_template, "1"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_2,
+ context.getString(R.string.keyboard_key_numpad_template, "2"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_3,
+ context.getString(R.string.keyboard_key_numpad_template, "3"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_4,
+ context.getString(R.string.keyboard_key_numpad_template, "4"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_5,
+ context.getString(R.string.keyboard_key_numpad_template, "5"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_6,
+ context.getString(R.string.keyboard_key_numpad_template, "6"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_7,
+ context.getString(R.string.keyboard_key_numpad_template, "7"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_8,
+ context.getString(R.string.keyboard_key_numpad_template, "8"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_9,
+ context.getString(R.string.keyboard_key_numpad_template, "9"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
+ context.getString(R.string.keyboard_key_numpad_template, "/"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
+ context.getString(R.string.keyboard_key_numpad_template, "*"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
+ context.getString(R.string.keyboard_key_numpad_template, "-"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ADD,
+ context.getString(R.string.keyboard_key_numpad_template, "+"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DOT,
+ context.getString(R.string.keyboard_key_numpad_template, "."));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
+ context.getString(R.string.keyboard_key_numpad_template, ","));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
+ context.getString(R.string.keyboard_key_numpad_template,
+ context.getString(R.string.keyboard_key_enter)));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
+ context.getString(R.string.keyboard_key_numpad_template, "="));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
+ context.getString(R.string.keyboard_key_numpad_template, "("));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
+ context.getString(R.string.keyboard_key_numpad_template, ")"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_EISU, "英数");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+
+ mModifierNames.put(KeyEvent.META_META_ON, "Meta");
+ mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
+ mModifierNames.put(KeyEvent.META_ALT_ON, "Alt");
+ mModifierNames.put(KeyEvent.META_SHIFT_ON, "Shift");
+ mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
+ mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
+
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
+
+ mModifierDrawables.put(
+ KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
+ }
+
+ private void createHardcodedShortcuts() {
+ // Add system shortcuts
+ mKeySearchResultMap.put(SHORTCUT_SYSTEM_INDEX, true);
+ mSystemGroup.add(getMultiMappingSystemShortcuts(mContext));
+ mSystemGroup.add(getSystemMultitaskingShortcuts(mContext));
+ // Add input shortcuts
+ mKeySearchResultMap.put(SHORTCUT_INPUT_INDEX, true);
+ mInputGroup.add(getMultiMappingInputShortcuts(mContext));
+ // Add open apps shortcuts
+ final List<KeyboardShortcutMultiMappingGroup> appShortcuts =
+ Arrays.asList(getDefaultMultiMappingApplicationShortcuts());
+ if (appShortcuts != null && !appShortcuts.isEmpty()) {
+ mOpenAppsGroup = appShortcuts;
+ mKeySearchResultMap.put(SHORTCUT_OPENAPPS_INDEX, true);
+ } else {
+ mKeySearchResultMap.put(SHORTCUT_OPENAPPS_INDEX, false);
+ }
+ }
+
+ /**
+ * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
+ * existing device, that device's map is used. Otherwise, it checks first all available devices
+ * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
+ * Keyboard with its default map.
+ */
+ private void retrieveKeyCharacterMap(int deviceId) {
+ final InputManager inputManager = InputManager.getInstance();
+ mBackupKeyCharacterMap = inputManager.getInputDevice(-1).getKeyCharacterMap();
+ if (deviceId != -1) {
+ final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (inputDevice != null) {
+ mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+ return;
+ }
+ }
+ final int[] deviceIds = inputManager.getInputDeviceIds();
+ for (int id : deviceIds) {
+ final InputDevice inputDevice = inputManager.getInputDevice(id);
+ // -1 is the Virtual Keyboard, with the default key map. Use that one only as last
+ // resort.
+ if (inputDevice.getId() != -1 && inputDevice.isFullKeyboard()) {
+ mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+ return;
+ }
+ }
+ // Fall back to -1, the virtual keyboard.
+ mKeyCharacterMap = mBackupKeyCharacterMap;
+ }
+
+ @VisibleForTesting
+ void showKeyboardShortcuts(int deviceId) {
+ retrieveKeyCharacterMap(deviceId);
+ mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
+ @Override
+ public void onKeyboardShortcutsReceived(
+ final List<KeyboardShortcutGroup> result) {
+ // Add specific app shortcuts
+ if (result.isEmpty()) {
+ mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
+ } else {
+ mSpecificAppGroup = reMapToKeyboardShortcutMultiMappingGroup(result);
+ mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
+ }
+ mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
+ mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
+ mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
+ mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
+ showKeyboardShortcutSearchList(mFullShortsGroup);
+ }
+ }, deviceId);
+ }
+
+ // The original data structure is only for 1-to-1 shortcut mapping, so remap the old
+ // data structure to the new data structure for handling the N-to-1 key mapping and other
+ // complex case.
+ private List<KeyboardShortcutMultiMappingGroup> reMapToKeyboardShortcutMultiMappingGroup(
+ List<KeyboardShortcutGroup> keyboardShortcutGroups) {
+ List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups =
+ new ArrayList<>();
+ for (KeyboardShortcutGroup group : keyboardShortcutGroups) {
+ CharSequence categoryTitle = group.getLabel();
+ List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
+ for (KeyboardShortcutInfo info : group.getItems()) {
+ shortcutMultiMappingInfos.add(new ShortcutMultiMappingInfo(info));
+ }
+ keyboardShortcutMultiMappingGroups.add(
+ new KeyboardShortcutMultiMappingGroup(
+ categoryTitle, shortcutMultiMappingInfos));
+ }
+ return keyboardShortcutMultiMappingGroups;
+ }
+
+ private void dismissKeyboardShortcuts() {
+ if (mKeyboardShortcutsBottomSheetDialog != null) {
+ mKeyboardShortcutsBottomSheetDialog.dismiss();
+ mKeyboardShortcutsBottomSheetDialog = null;
+ }
+ }
+
+ private KeyboardShortcutMultiMappingGroup getMultiMappingSystemShortcuts(Context context) {
+ KeyboardShortcutMultiMappingGroup systemGroup =
+ new KeyboardShortcutMultiMappingGroup(
+ context.getString(R.string.keyboard_shortcut_group_system),
+ new ArrayList<>());
+ List<ShortcutKeyGroupMultiMappingInfo> infoList = Arrays.asList(
+ /* Access notification shade: Meta + N */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_access_notification_shade),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_N, KeyEvent.META_META_ON))),
+ /* Take a full screenshot: Meta + Ctrl + S */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_full_screenshot),
+ Arrays.asList(
+ Pair.create(
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))),
+ /* Access list of system / apps shortcuts: Meta + / */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_access_system_app_shortcuts),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))),
+ /* Back: go back to previous state (back button) */
+ /* Meta + ~, Meta + backspace, Meta + left arrow */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_go_back),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_GRAVE, KeyEvent.META_META_ON),
+ Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON),
+ Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))),
+ /* Access home screen: Meta + H, Meta + Enter */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_access_home_screen),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_H, KeyEvent.META_META_ON),
+ Pair.create(KeyEvent.KEYCODE_ENTER, KeyEvent.META_META_ON))),
+ /* Overview of open apps: Meta + Tab */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_overview_open_apps),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))),
+ /* Cycle through recent apps (forward): Alt + Tab */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_cycle_forward),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON))),
+ /* Cycle through recent apps (back): Shift + Alt + Tab */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_cycle_back),
+ Arrays.asList(
+ Pair.create(
+ KeyEvent.KEYCODE_TAB,
+ KeyEvent.META_SHIFT_ON | KeyEvent.META_ALT_ON))),
+ /* Access list of all apps and search (i.e. Search/Launcher): Meta */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_access_all_apps_search),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.META_META_ON))),
+ /* Hide and (re)show taskbar: Meta + T */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_hide_reshow_taskbar),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON))),
+ /* Access system settings: Meta + I */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_access_system_settings),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_I, KeyEvent.META_META_ON))),
+ /* Access Google Assistant: Meta + A */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_access_google_assistant),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))),
+ /* Lock screen: Meta + L */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_lock_screen),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))),
+ /* Pull up Notes app for quick memo: Meta + Ctrl + N */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_quick_memo),
+ Arrays.asList(
+ Pair.create(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON)))
+ );
+ for (ShortcutKeyGroupMultiMappingInfo info : infoList) {
+ systemGroup.addItem(info.getShortcutMultiMappingInfo());
+ }
+ return systemGroup;
+ }
+
+ private static class ShortcutKeyGroupMultiMappingInfo {
+ private String mLabel;
+ private List<Pair<Integer, Integer>> mKeycodeGroupList;
+
+ ShortcutKeyGroupMultiMappingInfo(
+ String label, List<Pair<Integer, Integer>> keycodeGroupList) {
+ mLabel = label;
+ mKeycodeGroupList = keycodeGroupList;
+ }
+
+ ShortcutMultiMappingInfo getShortcutMultiMappingInfo() {
+ List<ShortcutKeyGroup> shortcutKeyGroups = new ArrayList<>();
+ for (Pair<Integer, Integer> keycodeGroup : mKeycodeGroupList) {
+ shortcutKeyGroups.add(new ShortcutKeyGroup(
+ new KeyboardShortcutInfo(
+ mLabel,
+ keycodeGroup.first /* keycode */,
+ keycodeGroup.second /* modifiers*/),
+ null));
+ }
+ ShortcutMultiMappingInfo shortcutMultiMappingInfo =
+ new ShortcutMultiMappingInfo(mLabel, null, shortcutKeyGroups);
+ return shortcutMultiMappingInfo;
+ }
+ }
+
+ private static KeyboardShortcutMultiMappingGroup getSystemMultitaskingShortcuts(
+ Context context) {
+ KeyboardShortcutMultiMappingGroup systemMultitaskingGroup =
+ new KeyboardShortcutMultiMappingGroup(
+ context.getString(R.string.keyboard_shortcut_group_system_multitasking),
+ new ArrayList<>());
+
+ // System multitasking shortcuts:
+ // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow
+ // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow
+ // Switch from Split screen to full screen: Meta + Ctrl + Up arrow
+ // During Split screen: replace an app from one to another: Meta + Ctrl + Down arrow
+ String[] shortcutLabels = {
+ context.getString(R.string.system_multitasking_rhs),
+ context.getString(R.string.system_multitasking_lhs),
+ context.getString(R.string.system_multitasking_full_screen),
+ context.getString(R.string.system_multitasking_replace)
+ };
+ int[] keyCodes = {
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN
+ };
+
+ for (int i = 0; i < shortcutLabels.length; i++) {
+ List<ShortcutKeyGroup> shortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(
+ new KeyboardShortcutInfo(
+ shortcutLabels[i],
+ keyCodes[i],
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON),
+ null));
+ ShortcutMultiMappingInfo shortcutMultiMappingInfo =
+ new ShortcutMultiMappingInfo(
+ shortcutLabels[i],
+ null,
+ shortcutKeyGroups);
+ systemMultitaskingGroup.addItem(shortcutMultiMappingInfo);
+ }
+ return systemMultitaskingGroup;
+ }
+
+ private static KeyboardShortcutMultiMappingGroup getMultiMappingInputShortcuts(
+ Context context) {
+ List<ShortcutMultiMappingInfo> shortcutMultiMappingInfoList = Arrays.asList(
+ /* Switch input language (next language): Ctrl + Space or Meta + Space */
+ new ShortcutMultiMappingInfo(
+ context.getString(R.string.input_switch_input_language_next),
+ null,
+ Arrays.asList(
+ new ShortcutKeyGroup(new KeyboardShortcutInfo(
+ context.getString(
+ R.string.input_switch_input_language_next),
+ KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
+ null),
+ new ShortcutKeyGroup(new KeyboardShortcutInfo(
+ context.getString(
+ R.string.input_switch_input_language_next),
+ KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON),
+ null))),
+ /* Switch input language (previous language): */
+ /* Ctrl + Shift + Space or Meta + Shift + Space */
+ new ShortcutMultiMappingInfo(
+ context.getString(R.string.input_switch_input_language_previous),
+ null,
+ Arrays.asList(
+ new ShortcutKeyGroup(new KeyboardShortcutInfo(
+ context.getString(
+ R.string.input_switch_input_language_previous),
+ KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
+ null),
+ new ShortcutKeyGroup(new KeyboardShortcutInfo(
+ context.getString(
+ R.string.input_switch_input_language_previous),
+ KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON),
+ null))),
+ /* Access emoji: Meta + . */
+ new ShortcutMultiMappingInfo(
+ context.getString(R.string.input_access_emoji),
+ null,
+ Arrays.asList(
+ new ShortcutKeyGroup(new KeyboardShortcutInfo(
+ context.getString(R.string.input_access_emoji),
+ KeyEvent.KEYCODE_PERIOD,
+ KeyEvent.META_META_ON),
+ null))),
+ /* Access voice typing: Meta + V */
+ new ShortcutMultiMappingInfo(
+ context.getString(R.string.input_access_voice_typing),
+ null,
+ Arrays.asList(
+ new ShortcutKeyGroup(new KeyboardShortcutInfo(
+ context.getString(R.string.input_access_voice_typing),
+ KeyEvent.KEYCODE_V, KeyEvent.META_META_ON),
+ null)))
+ );
+ return new KeyboardShortcutMultiMappingGroup(
+ context.getString(R.string.keyboard_shortcut_group_input),
+ shortcutMultiMappingInfoList);
+ }
+
+ private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts() {
+ final int userId = mContext.getUserId();
+ PackageInfo assistPackageInfo = getAssistPackageInfo(mContext, mPackageManager, userId);
+ CharSequence categoryTitle =
+ mContext.getString(R.string.keyboard_shortcut_group_applications);
+ List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
+
+ String[] intentCategories = {
+ Intent.CATEGORY_APP_BROWSER,
+ Intent.CATEGORY_APP_CONTACTS,
+ Intent.CATEGORY_APP_EMAIL,
+ Intent.CATEGORY_APP_CALENDAR,
+ Intent.CATEGORY_APP_MAPS,
+ Intent.CATEGORY_APP_MUSIC,
+ Intent.CATEGORY_APP_MESSAGING,
+ Intent.CATEGORY_APP_CALCULATOR,
+
+ };
+ String[] shortcutLabels = {
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
+ };
+ int[] keyCodes = {
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_U,
+ };
+
+ // Assist.
+ if (assistPackageInfo != null) {
+ if (assistPackageInfo != null) {
+ final Icon assistIcon = Icon.createWithResource(
+ assistPackageInfo.applicationInfo.packageName,
+ assistPackageInfo.applicationInfo.icon);
+ CharSequence assistLabel =
+ mContext.getString(R.string.keyboard_shortcut_group_applications_assist);
+ KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
+ assistLabel,
+ assistIcon,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON);
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(
+ assistLabel,
+ assistIcon,
+ Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ }
+ }
+
+ // Browser (Chrome as default): Meta + B
+ // Contacts: Meta + C
+ // Email (Gmail as default): Meta + E
+ // Gmail: Meta + G
+ // Calendar: Meta + K
+ // Maps: Meta + M
+ // Music: Meta + P
+ // SMS: Meta + S
+ // Calculator: Meta + U
+ for (int i = 0; i < shortcutLabels.length; i++) {
+ final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
+ if (icon != null) {
+ CharSequence label =
+ shortcutLabels[i];
+ KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
+ label,
+ icon,
+ keyCodes[i],
+ KeyEvent.META_META_ON);
+ List<ShortcutKeyGroup> shortcutKeyGroups =
+ Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ }
+ }
+
+ Comparator<ShortcutMultiMappingInfo> applicationItemsComparator =
+ new Comparator<ShortcutMultiMappingInfo>() {
+ @Override
+ public int compare(
+ ShortcutMultiMappingInfo ksh1, ShortcutMultiMappingInfo ksh2) {
+ boolean ksh1ShouldBeLast = ksh1.getLabel() == null
+ || ksh1.getLabel().toString().isEmpty();
+ boolean ksh2ShouldBeLast = ksh2.getLabel() == null
+ || ksh2.getLabel().toString().isEmpty();
+ if (ksh1ShouldBeLast && ksh2ShouldBeLast) {
+ return 0;
+ }
+ if (ksh1ShouldBeLast) {
+ return 1;
+ }
+ if (ksh2ShouldBeLast) {
+ return -1;
+ }
+ return (ksh1.getLabel().toString()).compareToIgnoreCase(
+ ksh2.getLabel().toString());
+ }
+ };
+ // Sorts by label, case insensitive with nulls and/or empty labels last.
+ Collections.sort(shortcutMultiMappingInfos, applicationItemsComparator);
+ return new KeyboardShortcutMultiMappingGroup(categoryTitle, shortcutMultiMappingInfos);
+ }
+
+ private Icon getIconForIntentCategory(String intentCategory, int userId) {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(intentCategory);
+
+ final PackageInfo packageInfo = getPackageInfoForIntent(intent, userId);
+ if (packageInfo != null && packageInfo.applicationInfo.icon != 0) {
+ return Icon.createWithResource(
+ packageInfo.applicationInfo.packageName,
+ packageInfo.applicationInfo.icon);
+ }
+ return null;
+ }
+
+ private PackageInfo getPackageInfoForIntent(Intent intent, int userId) {
+ try {
+ ResolveInfo handler;
+ handler = mPackageManager.resolveIntent(
+ intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0, userId);
+ if (handler == null || handler.activityInfo == null) {
+ return null;
+ }
+ return mPackageManager.getPackageInfo(handler.activityInfo.packageName, 0, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManagerService is dead", e);
+ return null;
+ }
+ }
+
+ private void showKeyboardShortcutSearchList(
+ List<List<KeyboardShortcutMultiMappingGroup>> keyboardShortcutMultiMappingGroupList) {
+ // Need to post on the main thread.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleShowKeyboardShortcutSearchList(keyboardShortcutMultiMappingGroupList);
+ }
+ });
+ }
+
+ private void handleShowKeyboardShortcutSearchList(
+ List<List<KeyboardShortcutMultiMappingGroup>> keyboardShortcutMultiMappingGroupList) {
+ mQueryString = null;
+ LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+ mKeyboardShortcutsBottomSheetDialog =
+ new BottomSheetDialog(mContext);
+ final View keyboardShortcutsView = inflater.inflate(
+ R.layout.keyboard_shortcuts_search_view, null);
+ mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result);
+ mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView);
+ setButtonsDefaultStatus(keyboardShortcutsView);
+ populateKeyboardShortcutSearchList(
+ keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_container));
+
+ // Workaround for solve issue about dialog not full expanded when landscape.
+ FrameLayout bottomSheet = (FrameLayout)
+ mKeyboardShortcutsBottomSheetDialog.findViewById(
+ com.google.android.material.R.id.design_bottom_sheet);
+ if (bottomSheet != null) {
+ bottomSheet.setBackgroundResource(android.R.color.transparent);
+ }
+
+ BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+ behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ behavior.setSkipCollapsed(true);
+
+ mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
+ Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
+ keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
+ synchronized (sLock) {
+ // show KeyboardShortcutsBottomSheetDialog only if it has not been dismissed already
+ if (sInstance != null) {
+ mKeyboardShortcutsBottomSheetDialog.show();
+ setDialogScreenSize();
+ keyboardShortcutsView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ setDialogScreenSize();
+ }
+ });
+ }
+ }
+ mSearchEditText = keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_search);
+ mSearchEditText.addTextChangedListener(
+ new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ mQueryString = s.toString();
+ populateKeyboardShortcutSearchList(
+ keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_container));
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing.
+ }
+ });
+ mEditTextCancel = keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_search_cancel);
+ mEditTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
+ }
+
+ private void populateKeyboardShortcutSearchList(LinearLayout keyboardShortcutsLayout) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ TextView shortcutsKeyView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_view, keyboardShortcutsLayout, false);
+ shortcutsKeyView.measure(
+ View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ final int shortcutKeyTextItemMinWidth = shortcutsKeyView.getMeasuredHeight();
+ // Needed to be able to scale the image items to the same height as the text items.
+ final int shortcutKeyIconItemHeightWidth = shortcutsKeyView.getMeasuredHeight()
+ - shortcutsKeyView.getPaddingTop()
+ - shortcutsKeyView.getPaddingBottom();
+ keyboardShortcutsLayout.removeAllViews();
+
+ // Search if user's input is contained in any shortcut groups.
+ if (mQueryString != null) {
+ for (int i = 0; i < mFullShortsGroup.size(); i++) {
+ mKeySearchResultMap.put(i, false);
+ for (KeyboardShortcutMultiMappingGroup group : mFullShortsGroup.get(i)) {
+ for (ShortcutMultiMappingInfo info : group.getItems()) {
+ String itemLabel = info.getLabel().toString();
+ if (itemLabel.toUpperCase(Locale.getDefault()).contains(
+ mQueryString.toUpperCase(Locale.getDefault()))) {
+ mKeySearchResultMap.put(i, true);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Set default color for the non-focus categories.
+ for (int i = 0; i < mKeySearchResultMap.size(); i++) {
+ if (mKeySearchResultMap.get(i)) {
+ mFullButtonList.get(i).setVisibility(View.VISIBLE);
+ setButtonFocusColor(i, false);
+ } else {
+ mFullButtonList.get(i).setVisibility(View.GONE);
+ }
+ }
+
+ // Move the focus to the suitable category.
+ if (mFullButtonList.get(mCurrentCategoryIndex).getVisibility() == View.GONE) {
+ for (int i = 0; i < mKeySearchResultMap.size(); i++) {
+ if (mKeySearchResultMap.get(i)) {
+ setCurrentCategoryIndex(i);
+ break;
+ }
+ }
+ }
+
+ // Set color for the current focus category
+ setButtonFocusColor(mCurrentCategoryIndex, true);
+
+ // Load shortcuts for current focus category.
+ List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups =
+ mFullShortsGroup.get(mCurrentCategoryIndex);
+
+ int keyboardShortcutGroupsSize = keyboardShortcutMultiMappingGroups.size();
+ List<Boolean> groupSearchResult = new ArrayList<>();
+ for (int i = 0; i < keyboardShortcutGroupsSize; i++) {
+ View separator = inflater.inflate(
+ R.layout.keyboard_shortcuts_category_short_separator,
+ keyboardShortcutsLayout,
+ false);
+
+ // If there are more than one category, add separators among categories.
+ if (i > 0) {
+ keyboardShortcutsLayout.addView(separator);
+ }
+
+ List<Boolean> itemSearchResult = new ArrayList<>();
+ KeyboardShortcutMultiMappingGroup categoryGroup =
+ keyboardShortcutMultiMappingGroups.get(i);
+ TextView categoryTitle = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_category_title, keyboardShortcutsLayout, false);
+ categoryTitle.setText(categoryGroup.getCategory());
+ keyboardShortcutsLayout.addView(categoryTitle);
+ LinearLayout shortcutContainer = (LinearLayout) inflater.inflate(
+ R.layout.keyboard_shortcuts_container, keyboardShortcutsLayout, false);
+ final int itemsSize = categoryGroup.getItems().size();
+ for (int j = 0; j < itemsSize; j++) {
+ ShortcutMultiMappingInfo keyGroupInfo = categoryGroup.getItems().get(j);
+
+ if (mQueryString != null) {
+ String shortcutLabel =
+ keyGroupInfo.getLabel().toString().toUpperCase(Locale.getDefault());
+ String queryString = mQueryString.toUpperCase(Locale.getDefault());
+ if (!shortcutLabel.contains(queryString)) {
+ itemSearchResult.add(j, false);
+ continue;
+ } else {
+ itemSearchResult.add(j, true);
+ }
+ }
+
+ View shortcutView = inflater.inflate(R.layout.keyboard_shortcut_app_item,
+ shortcutContainer, false);
+ TextView shortcutKeyword =
+ shortcutView.findViewById(R.id.keyboard_shortcuts_keyword);
+ shortcutKeyword.setText(keyGroupInfo.getLabel());
+
+ if (keyGroupInfo.getIcon() != null) {
+ ImageView shortcutIcon =
+ shortcutView.findViewById(R.id.keyboard_shortcuts_icon);
+ shortcutIcon.setImageIcon(keyGroupInfo.getIcon());
+ shortcutIcon.setVisibility(View.VISIBLE);
+ RelativeLayout.LayoutParams lp =
+ (RelativeLayout.LayoutParams) shortcutKeyword.getLayoutParams();
+ lp.removeRule(RelativeLayout.ALIGN_PARENT_START);
+ shortcutKeyword.setLayoutParams(lp);
+ }
+
+ ViewGroup shortcutItemsContainer =
+ shortcutView.findViewById(R.id.keyboard_shortcuts_item_container);
+ final int keyGroupItemsSize = keyGroupInfo.getShortcutKeyGroups().size();
+ for (int p = 0; p < keyGroupItemsSize; p++) {
+ KeyboardShortcutInfo keyboardShortcutInfo =
+ keyGroupInfo.getShortcutKeyGroups().get(p).getKeyboardShortcutInfo();
+ String complexCommand =
+ keyGroupInfo.getShortcutKeyGroups().get(p).getComplexCommand();
+
+ if (complexCommand == null) {
+ List<StringDrawableContainer> shortcutKeys =
+ getHumanReadableShortcutKeys(keyboardShortcutInfo);
+ if (shortcutKeys == null) {
+ // Ignore shortcuts we can't display keys for.
+ Log.w(TAG, "Keyboard Shortcut contains unsupported keys, skipping.");
+ continue;
+ }
+ final int shortcutKeysSize = shortcutKeys.size();
+ for (int k = 0; k < shortcutKeysSize; k++) {
+ StringDrawableContainer shortcutRepresentation = shortcutKeys.get(k);
+ if (shortcutRepresentation.mDrawable != null) {
+ ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_new_icon_view,
+ shortcutItemsContainer,
+ false);
+ Bitmap bitmap = Bitmap.createBitmap(shortcutKeyIconItemHeightWidth,
+ shortcutKeyIconItemHeightWidth, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ shortcutRepresentation.mDrawable.setBounds(0, 0, canvas.getWidth(),
+ canvas.getHeight());
+ shortcutRepresentation.mDrawable.draw(canvas);
+ shortcutKeyIconView.setImageBitmap(bitmap);
+ shortcutKeyIconView.setImportantForAccessibility(
+ IMPORTANT_FOR_ACCESSIBILITY_YES);
+ shortcutKeyIconView.setAccessibilityDelegate(
+ new ShortcutKeyAccessibilityDelegate(
+ shortcutRepresentation.mString));
+ shortcutItemsContainer.addView(shortcutKeyIconView);
+ } else if (shortcutRepresentation.mString != null) {
+ TextView shortcutKeyTextView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_new_view,
+ shortcutItemsContainer,
+ false);
+ shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
+ shortcutKeyTextView.setText(shortcutRepresentation.mString);
+ shortcutKeyTextView.setAccessibilityDelegate(
+ new ShortcutKeyAccessibilityDelegate(
+ shortcutRepresentation.mString));
+ shortcutItemsContainer.addView(shortcutKeyTextView);
+ }
+
+ if (k < shortcutKeysSize - 1) {
+ TextView shortcutKeyTextView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_plus_view,
+ shortcutItemsContainer,
+ false);
+ shortcutItemsContainer.addView(shortcutKeyTextView);
+ }
+ }
+ } else {
+ TextView shortcutKeyTextView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_new_view,
+ shortcutItemsContainer,
+ false);
+ shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
+ shortcutKeyTextView.setText(complexCommand);
+ shortcutKeyTextView.setAccessibilityDelegate(
+ new ShortcutKeyAccessibilityDelegate(complexCommand));
+ shortcutItemsContainer.addView(shortcutKeyTextView);
+ }
+
+ if (p < keyGroupItemsSize - 1) {
+ TextView shortcutKeyTextView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_vertical_bar_view,
+ shortcutItemsContainer,
+ false);
+ shortcutItemsContainer.addView(shortcutKeyTextView);
+ }
+ }
+ shortcutContainer.addView(shortcutView);
+ }
+
+ if (!groupSearchResult.isEmpty() && !groupSearchResult.get(i - 1)) {
+ keyboardShortcutsLayout.removeView(separator);
+ }
+
+ if (!itemSearchResult.isEmpty() && !itemSearchResult.contains(true)) {
+ // No results, so remove the category title and separator
+ keyboardShortcutsLayout.removeView(categoryTitle);
+ keyboardShortcutsLayout.removeView(separator);
+ groupSearchResult.add(false);
+ if (i == keyboardShortcutGroupsSize - 1 && !groupSearchResult.contains(true)) {
+ // show "No shortcut found"
+ mNoSearchResults.setVisibility(View.VISIBLE);
+ }
+ continue;
+ }
+ groupSearchResult.add(true);
+ mNoSearchResults.setVisibility(View.GONE);
+ keyboardShortcutsLayout.addView(shortcutContainer);
+ }
+ }
+
+ private List<StringDrawableContainer> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
+ List<StringDrawableContainer> shortcutKeys = getHumanReadableModifiers(info);
+ if (shortcutKeys == null) {
+ return null;
+ }
+ String shortcutKeyString = null;
+ Drawable shortcutKeyDrawable = null;
+ if (info.getBaseCharacter() > Character.MIN_VALUE) {
+ shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
+ shortcutKeyDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
+ shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
+ } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
+ shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
+ } else {
+ // Special case for shortcuts with no base key or keycode.
+ if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
+ return shortcutKeys;
+ }
+ char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
+ if (displayLabel != 0) {
+ shortcutKeyString = String.valueOf(displayLabel);
+ } else {
+ displayLabel = mBackupKeyCharacterMap.getDisplayLabel(info.getKeycode());
+ if (displayLabel != 0) {
+ shortcutKeyString = String.valueOf(displayLabel);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ if (shortcutKeyString != null) {
+ shortcutKeys.add(new StringDrawableContainer(shortcutKeyString, shortcutKeyDrawable));
+ } else {
+ Log.w(TAG, "Keyboard Shortcut does not have a text representation, skipping.");
+ }
+
+ return shortcutKeys;
+ }
+
+ private List<StringDrawableContainer> getHumanReadableModifiers(KeyboardShortcutInfo info) {
+ final List<StringDrawableContainer> shortcutKeys = new ArrayList<>();
+ int modifiers = info.getModifiers();
+ if (modifiers == 0) {
+ return shortcutKeys;
+ }
+ for (int supportedModifier : mModifierList) {
+ if ((modifiers & supportedModifier) != 0) {
+ shortcutKeys.add(new StringDrawableContainer(
+ mModifierNames.get(supportedModifier),
+ mModifierDrawables.get(supportedModifier)));
+ modifiers &= ~supportedModifier;
+ }
+ }
+ if (modifiers != 0) {
+ // Remaining unsupported modifiers, don't show anything.
+ return null;
+ }
+ return shortcutKeys;
+ }
+
+ private final class ShortcutKeyAccessibilityDelegate extends AccessibilityDelegate {
+ private String mContentDescription;
+
+ ShortcutKeyAccessibilityDelegate(String contentDescription) {
+ mContentDescription = contentDescription;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ if (mContentDescription != null) {
+ info.setContentDescription(mContentDescription.toLowerCase(Locale.getDefault()));
+ }
+ }
+ }
+
+ private static final class StringDrawableContainer {
+ @NonNull
+ public String mString;
+ @Nullable
+ public Drawable mDrawable;
+
+ StringDrawableContainer(String string, Drawable drawable) {
+ mString = string;
+ mDrawable = drawable;
+ }
+ }
+
+ private void setDialogScreenSize() {
+ Window window = mKeyboardShortcutsBottomSheetDialog.getWindow();
+ Display display = mWindowManager.getDefaultDisplay();
+ WindowManager.LayoutParams lp =
+ mKeyboardShortcutsBottomSheetDialog.getWindow().getAttributes();
+ if (mContext.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT) {
+ lp.width = (int) (display.getWidth() * 0.8);
+ lp.height = (int) (display.getHeight() * 0.7);
+ } else {
+ lp.width = (int) (display.getWidth() * 0.7);
+ lp.height = (int) (display.getHeight() * 0.8);
+ }
+ window.setGravity(Gravity.BOTTOM);
+ window.setAttributes(lp);
+ }
+
+ private void setCurrentCategoryIndex(int index) {
+ mCurrentCategoryIndex = index;
+ }
+
+ private void setButtonsDefaultStatus(View keyboardShortcutsView) {
+ mButtonSystem = keyboardShortcutsView.findViewById(R.id.shortcut_system);
+ mButtonInput = keyboardShortcutsView.findViewById(R.id.shortcut_input);
+ mButtonOpenApps = keyboardShortcutsView.findViewById(R.id.shortcut_open_apps);
+ mButtonSpecificApp = keyboardShortcutsView.findViewById(R.id.shortcut_specific_app);
+
+ mButtonSystem.setOnClickListener(v -> {
+ setCurrentCategoryIndex(SHORTCUT_SYSTEM_INDEX);
+ populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_container));
+ });
+
+ mButtonInput.setOnClickListener(v -> {
+ setCurrentCategoryIndex(SHORTCUT_INPUT_INDEX);
+ populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_container));
+ });
+
+ mButtonOpenApps.setOnClickListener(v -> {
+ setCurrentCategoryIndex(SHORTCUT_OPENAPPS_INDEX);
+ populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_container));
+ });
+
+ mButtonSpecificApp.setOnClickListener(v -> {
+ setCurrentCategoryIndex(SHORTCUT_SPECIFICAPP_INDEX);
+ populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_container));
+ });
+
+ mFullButtonList.add(mButtonSystem);
+ mFullButtonList.add(mButtonInput);
+ mFullButtonList.add(mButtonOpenApps);
+ mFullButtonList.add(mButtonSpecificApp);
+ }
+
+ private void setButtonFocusColor(int i, boolean isFocused) {
+ if (isFocused) {
+ mFullButtonList.get(i).setTextColor(getColorOfTextColorOnAccent());
+ mFullButtonList.get(i).setBackground(
+ mContext.getDrawable(R.drawable.shortcut_button_focus_colored));
+ } else {
+ // Default color
+ mFullButtonList.get(i).setTextColor(getColorOfTextColorSecondary());
+ mFullButtonList.get(i).setBackground(
+ mContext.getDrawable(R.drawable.shortcut_button_colored));
+ }
+ }
+
+ private int getColorOfTextColorOnAccent() {
+ return Utils.getColorAttrDefaultColor(
+ mContext, com.android.internal.R.attr.textColorOnAccent);
+ }
+
+ private int getColorOfTextColorSecondary() {
+ return Utils.getColorAttrDefaultColor(
+ mContext, com.android.internal.R.attr.textColorSecondary);
+ }
+
+ // Create the new data structure for handling the N-to-1 key mapping and other complex case.
+ private static class KeyboardShortcutMultiMappingGroup {
+ private final CharSequence mCategory;
+ private List<ShortcutMultiMappingInfo> mItems;
+
+ KeyboardShortcutMultiMappingGroup(
+ CharSequence category, List<ShortcutMultiMappingInfo> items) {
+ mCategory = category;
+ mItems = items;
+ }
+
+ void addItem(ShortcutMultiMappingInfo item) {
+ mItems.add(item);
+ }
+
+ CharSequence getCategory() {
+ return mCategory;
+ }
+
+ List<ShortcutMultiMappingInfo> getItems() {
+ return mItems;
+ }
+ }
+
+ private static class ShortcutMultiMappingInfo {
+ private final CharSequence mLabel;
+ private final Icon mIcon;
+ private List<ShortcutKeyGroup> mShortcutKeyGroups;
+
+ ShortcutMultiMappingInfo(
+ CharSequence label, Icon icon, List<ShortcutKeyGroup> shortcutKeyGroups) {
+ mLabel = label;
+ mIcon = icon;
+ mShortcutKeyGroups = shortcutKeyGroups;
+ }
+
+ ShortcutMultiMappingInfo(KeyboardShortcutInfo info) {
+ mLabel = info.getLabel();
+ mIcon = info.getIcon();
+ mShortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(info, null));
+ }
+
+ CharSequence getLabel() {
+ return mLabel;
+ }
+
+ Icon getIcon() {
+ return mIcon;
+ }
+
+ List<ShortcutKeyGroup> getShortcutKeyGroups() {
+ return mShortcutKeyGroups;
+ }
+ }
+
+ private static class ShortcutKeyGroup {
+ private final KeyboardShortcutInfo mKeyboardShortcutInfo;
+ private final String mComplexCommand;
+
+ ShortcutKeyGroup(KeyboardShortcutInfo keyboardShortcutInfo, String complexCommand) {
+ mKeyboardShortcutInfo = keyboardShortcutInfo;
+ mComplexCommand = complexCommand;
+ }
+
+ // To be compatible with the original functions, keep KeyboardShortcutInfo in here.
+ KeyboardShortcutInfo getKeyboardShortcutInfo() {
+ return mKeyboardShortcutInfo;
+ }
+
+ // In some case, the shortcut is a complex description not a N-to-1 key mapping.
+ String getComplexCommand() {
+ return mComplexCommand;
+ }
+ }
+
+ private static PackageInfo getAssistPackageInfo(
+ Context context, IPackageManager packageManager, int userId) {
+ AssistUtils assistUtils = new AssistUtils(context);
+ ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
+ // Not all devices have an assist component.
+ PackageInfo assistPackageInfo = null;
+ if (assistComponent != null) {
+ try {
+ assistPackageInfo = packageManager.getPackageInfo(
+ assistComponent.getPackageName(), 0, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManagerService is dead");
+ }
+ }
+ return assistPackageInfo;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 7e6ddcf..f20f929 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -63,6 +63,7 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -80,7 +81,8 @@
public final class KeyboardShortcuts {
private static final String TAG = KeyboardShortcuts.class.getSimpleName();
private static final Object sLock = new Object();
- private static KeyboardShortcuts sInstance;
+ @VisibleForTesting static KeyboardShortcuts sInstance;
+ private WindowManager mWindowManager;
private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
private final SparseArray<String> mModifierNames = new SparseArray<>();
@@ -94,7 +96,7 @@
};
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Context mContext;
+ @VisibleForTesting Context mContext;
private final IPackageManager mPackageManager;
private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
@@ -123,20 +125,26 @@
}
};
- private Dialog mKeyboardShortcutsDialog;
+ @VisibleForTesting Dialog mKeyboardShortcutsDialog;
private KeyCharacterMap mKeyCharacterMap;
private KeyCharacterMap mBackupKeyCharacterMap;
- private KeyboardShortcuts(Context context) {
+ @VisibleForTesting
+ KeyboardShortcuts(Context context, WindowManager windowManager) {
this.mContext = new ContextThemeWrapper(
context, android.R.style.Theme_DeviceDefault_Settings);
this.mPackageManager = AppGlobals.getPackageManager();
+ if (windowManager != null) {
+ this.mWindowManager = windowManager;
+ } else {
+ this.mWindowManager = mContext.getSystemService(WindowManager.class);
+ }
loadResources(context);
}
private static KeyboardShortcuts getInstance(Context context) {
if (sInstance == null) {
- sInstance = new KeyboardShortcuts(context);
+ sInstance = new KeyboardShortcuts(context, null);
}
return sInstance;
}
@@ -371,10 +379,10 @@
mKeyCharacterMap = mBackupKeyCharacterMap;
}
- private void showKeyboardShortcuts(int deviceId) {
+ @VisibleForTesting
+ void showKeyboardShortcuts(int deviceId) {
retrieveKeyCharacterMap(deviceId);
- WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- wm.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
+ mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
@Override
public void onKeyboardShortcutsReceived(
final List<KeyboardShortcutGroup> result) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
index 8a5bece..e9fac28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -19,16 +19,38 @@
import android.content.Context;
import android.content.Intent;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import javax.inject.Inject;
+
/**
* Receiver for the Keyboard Shortcuts Helper.
*/
public class KeyboardShortcutsReceiver extends BroadcastReceiver {
+
+ private boolean mIsShortcutListSearchEnabled;
+
+ @Inject
+ public KeyboardShortcutsReceiver(FeatureFlags featureFlags) {
+ mIsShortcutListSearchEnabled = featureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT);
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
- KeyboardShortcuts.show(context, -1 /* deviceId unknown */);
- } else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
- KeyboardShortcuts.dismiss();
+ if (mIsShortcutListSearchEnabled && Utilities.isTablet(context)) {
+ if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+ KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */);
+ } else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+ KeyboardShortcutListSearch.dismiss();
+ }
+ } else {
+ if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+ KeyboardShortcuts.show(context, -1 /* deviceId unknown */);
+ } else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+ KeyboardShortcuts.dismiss();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 250900e..d3927a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -185,6 +185,7 @@
private boolean mPowerCharged;
private boolean mBatteryOverheated;
private boolean mEnableBatteryDefender;
+ private boolean mIncompatibleCharger;
private int mChargingSpeed;
private int mChargingWattage;
private int mBatteryLevel;
@@ -903,6 +904,10 @@
chargingId = R.string.keyguard_plugged_in_charging_limited;
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
+ } else if (mPowerPluggedIn && mIncompatibleCharger) {
+ chargingId = R.string.keyguard_plugged_in_incompatible_charger;
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
+ return mContext.getResources().getString(chargingId, percentage);
} else if (mPowerCharged) {
return mContext.getResources().getString(R.string.keyguard_charged);
}
@@ -1063,6 +1068,7 @@
mBatteryPresent = status.present;
mBatteryOverheated = status.isOverheated();
mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn();
+ mIncompatibleCharger = status.incompatibleCharger.orElse(false);
try {
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
index 049321b..b318252 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
@@ -17,11 +17,10 @@
package com.android.systemui.statusbar.notification.collection.provider
import androidx.annotation.VisibleForTesting
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.util.asIndenting
import com.android.systemui.util.withIncreasedIndent
@@ -29,7 +28,9 @@
import javax.inject.Inject
@SysUISingleton
-class NotificationDismissibilityProviderImpl @Inject constructor(dumpManager: DumpManager) :
+class NotificationDismissibilityProviderImpl
+@Inject
+constructor(private val notifPipelineFlags: NotifPipelineFlags, dumpManager: DumpManager) :
NotificationDismissibilityProvider, Dumpable {
init {
@@ -42,8 +43,7 @@
private set
override fun isDismissable(entry: NotificationEntry): Boolean {
- // TODO(b/268380968): inject FlagResolver
- return if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(ALLOW_DISMISS_ONGOING)) {
+ return if (notifPipelineFlags.allowDismissOngoing()) {
entry.key !in nonDismissableEntryKeys
} else {
entry.legacyIsDismissableRecursive()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 13b3aca..27fe747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -70,7 +70,15 @@
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
}, {
- "No alerting: snoozed package: $str1"
+ "No heads up: snoozed package: $str1"
+ })
+ }
+
+ fun logHeadsUpPackageSnoozeBypassedHasFsi(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ }, {
+ "Heads up: package snooze bypassed because notification has full-screen intent: $str1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 9bcf92d..afeb72f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -19,6 +19,7 @@
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI;
import android.app.Notification;
import android.app.NotificationManager;
@@ -87,7 +88,10 @@
FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
@UiEvent(doc = "HUN suppressed for old when")
- HUN_SUPPRESSED_OLD_WHEN(1237);
+ HUN_SUPPRESSED_OLD_WHEN(1237),
+
+ @UiEvent(doc = "HUN snooze bypassed for potentially suppressed FSI")
+ HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI(1269);
private final int mId;
@@ -409,7 +413,15 @@
return false;
}
- if (isSnoozedPackage(sbn)) {
+ final boolean isSnoozedPackage = isSnoozedPackage(sbn);
+ final boolean fsiRequiresKeyguard = mFlags.fullScreenIntentRequiresKeyguard();
+ final boolean hasFsi = sbn.getNotification().fullScreenIntent != null;
+
+ // Assume any notification with an FSI is time-sensitive (like an alarm or incoming call)
+ // and ignore whether HUNs have been snoozed for the package.
+ final boolean shouldBypassSnooze = fsiRequiresKeyguard && hasFsi;
+
+ if (isSnoozedPackage && !shouldBypassSnooze) {
if (log) mLogger.logNoHeadsUpPackageSnoozed(entry);
return false;
}
@@ -447,6 +459,19 @@
return false;
}
}
+
+ if (isSnoozedPackage) {
+ if (log) {
+ mLogger.logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
+ mUiEventLogger.log(HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI, uid,
+ packageName);
+ }
+
+ return true;
+ }
+
if (log) mLogger.logHeadsUp(entry);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1966a66..6a52a33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -186,11 +186,13 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.KeyboardShortcutListSearch;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LiftReveal;
@@ -299,6 +301,7 @@
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private NotificationListContainer mNotifListContainer;
+ private boolean mIsShortcutListSearchEnabled;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
@@ -833,6 +836,7 @@
mCameraLauncherLazy = cameraLauncherLazy;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mUserTracker = userTracker;
+ mIsShortcutListSearchEnabled = featureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT);
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -2546,7 +2550,11 @@
String action = intent.getAction();
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- KeyboardShortcuts.dismiss();
+ if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) {
+ KeyboardShortcutListSearch.dismiss();
+ } else {
+ KeyboardShortcuts.dismiss();
+ }
mRemoteInputManager.closeRemoteInputs();
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
int flags = CommandQueue.FLAG_EXCLUDE_NONE;
@@ -3893,11 +3901,19 @@
}
protected void toggleKeyboardShortcuts(int deviceId) {
- KeyboardShortcuts.toggle(mContext, deviceId);
+ if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) {
+ KeyboardShortcutListSearch.toggle(mContext, deviceId);
+ } else {
+ KeyboardShortcuts.toggle(mContext, deviceId);
+ }
}
protected void dismissKeyboardShortcuts() {
- KeyboardShortcuts.dismiss();
+ if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) {
+ KeyboardShortcutListSearch.dismiss();
+ } else {
+ KeyboardShortcuts.dismiss();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
index 2f34516..16c4027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
+import android.os.ParcelUuid
+
/**
* SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the
* subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be
@@ -29,4 +31,7 @@
* filtering in certain cases. See [MobileIconsInteractor] for the filtering logic
*/
val isOpportunistic: Boolean = false,
+
+ /** Subscriptions in the same group may be filtered or treated as a single subscription */
+ val groupUuid: ParcelUuid? = null,
)
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 c9049d8..e77266f 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
@@ -376,6 +376,7 @@
SubscriptionModel(
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
+ groupUuid = groupUuid,
)
companion object {
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 72d5113..5a2e11e 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
@@ -150,6 +150,12 @@
val info1 = unfilteredSubs[0]
val info2 = unfilteredSubs[1]
+
+ // Filtering only applies to subscriptions in the same group
+ if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) {
+ return@combine unfilteredSubs
+ }
+
// If both subscriptions are primary, show both
if (!info1.isOpportunistic && !info2.isOpportunistic) {
return@combine unfilteredSubs
@@ -186,7 +192,7 @@
* validated bit from the old active network (A) while data is changing to the new one (B).
*
* This condition only applies if
- * 1. A and B are in the same subscription group (e.c. for CBRS data switching) and
+ * 1. A and B are in the same subscription group (e.g. for CBRS data switching) and
* 2. A was validated before the switch
*
* The goal of this is to minimize the flickering in the UI of the cellular indicator
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 3f54aebf..c0cbd62 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.hardware.BatteryState
import android.hardware.input.InputManager
+import android.hardware.input.InputSettings
import android.os.Handler
import android.util.ArrayMap
import android.util.Log
@@ -30,6 +31,9 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.shared.hardware.hasInputDevice
+import com.android.systemui.shared.hardware.isInternalStylusSource
+import com.android.systemui.statusbar.notification.collection.listbuilder.DEBUG
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -59,8 +63,10 @@
CopyOnWriteArrayList()
// This map should only be accessed on the handler
private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
- // This variable should only be accessed on the handler
+
+ // These variables should only be accessed on the handler
private var hasStarted: Boolean = false
+ private var isInUsiSession: Boolean = false
/**
* Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot
@@ -70,6 +76,10 @@
handler.post {
if (hasStarted) return@post
hasStarted = true
+ isInUsiSession =
+ inputManager.hasInputDevice {
+ it.isInternalStylusSource && isBatteryStateValid(it.batteryState)
+ }
addExistingStylusToMap()
inputManager.registerInputDeviceListener(this, handler)
@@ -177,7 +187,18 @@
handler.post {
if (!hasStarted) return@post
- if (batteryState.isPresent) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onBatteryStateChanged for $deviceId. " +
+ "batteryState present: ${batteryState.isPresent}, " +
+ "capacity: ${batteryState.capacity}"
+ )
+ }
+
+ val batteryStateValid = isBatteryStateValid(batteryState)
+ trackAndLogUsiSession(deviceId, batteryStateValid)
+ if (batteryStateValid) {
onStylusUsed()
}
@@ -215,12 +236,43 @@
*/
private fun onStylusUsed() {
if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
- if (inputManager.isStylusEverUsed(context)) return
+ if (InputSettings.isStylusEverUsed(context)) return
- inputManager.setStylusEverUsed(context, true)
+ InputSettings.setStylusEverUsed(context, true)
executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
}
+ /**
+ * Uses the input device battery state to track whether a current USI session is active. The
+ * InputDevice battery state updates USI battery on USI stylus input, and removes the last-known
+ * USI stylus battery presence after 1 hour of not detecting input. As SysUI and StylusManager
+ * is persistently running, relies on tracking sessions via an in-memory isInUsiSession boolean.
+ */
+ private fun trackAndLogUsiSession(deviceId: Int, batteryStateValid: Boolean) {
+ // TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus
+ // is sent after the actual valid callback
+ if (batteryStateValid && !isInUsiSession) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "USI battery newly present, entering new USI session. Device ID: $deviceId"
+ )
+ }
+ isInUsiSession = true
+ uiEventLogger.log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED)
+ } else if (!batteryStateValid && isInUsiSession) {
+ if (DEBUG) {
+ Log.d(TAG, "USI battery newly absent, exiting USI session Device ID: $deviceId")
+ }
+ isInUsiSession = false
+ uiEventLogger.log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED)
+ }
+ }
+
+ private fun isBatteryStateValid(batteryState: BatteryState): Boolean {
+ return batteryState.isPresent && batteryState.capacity > 0.0f
+ }
+
private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) {
stylusCallbacks.forEach(run)
}
@@ -295,5 +347,6 @@
companion object {
private val TAG = StylusManager::class.simpleName.orEmpty()
+ private val DEBUG = false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
index 99da4ce..e77749b 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
@@ -31,7 +31,11 @@
@UiEvent(doc = "UIEvent for Toast shown when stylus stopped charging")
STYLUS_STOPPED_CHARGING(1303),
@UiEvent(doc = "UIEvent for bluetooth stylus connected") BLUETOOTH_STYLUS_CONNECTED(1304),
- @UiEvent(doc = "UIEvent for bluetooth stylus disconnected") BLUETOOTH_STYLUS_DISCONNECTED(1305);
+ @UiEvent(doc = "UIEvent for bluetooth stylus disconnected") BLUETOOTH_STYLUS_DISCONNECTED(1305),
+ @UiEvent(doc = "UIEvent for start of a USI session via battery presence")
+ USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED(1306),
+ @UiEvent(doc = "UIEvent for end of a USI session via battery absence")
+ USI_STYLUS_BATTERY_PRESENCE_REMOVED(1307);
override fun getId() = _id
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
index dde2a80..27cafb1 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
@@ -74,6 +74,7 @@
stylusUsiPowerUi.init()
stylusManager.registerCallback(this)
+ stylusManager.registerBatteryCallback(this)
stylusManager.startListener()
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 696134c..a20a5b2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -16,6 +16,8 @@
package com.android.systemui.temporarydisplay.chipbar
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.os.PowerManager
@@ -27,11 +29,14 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.IdRes
+import androidx.annotation.VisibleForTesting
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -101,6 +106,15 @@
private lateinit var parent: ChipbarRootView
+ /** The current loading information, or null we're not currently loading. */
+ @VisibleForTesting
+ internal var loadingDetails: LoadingDetails? = null
+ private set(value) {
+ // Always cancel the old one before updating
+ field?.animator?.cancel()
+ field = value
+ }
+
override val windowLayoutParams =
commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) }
@@ -143,8 +157,22 @@
// ---- End item ----
// Loading
- currentView.requireViewById<View>(R.id.loading).visibility =
- (newInfo.endItem == ChipbarEndItem.Loading).visibleIfTrue()
+ val isLoading = newInfo.endItem == ChipbarEndItem.Loading
+ val loadingView = currentView.requireViewById<ImageView>(R.id.loading)
+ loadingView.visibility = isLoading.visibleIfTrue()
+
+ if (isLoading) {
+ val currentLoadingDetails = loadingDetails
+ // Since there can be multiple chipbars, we need to check if the loading view is the
+ // same and possibly re-start the loading animation on the new view.
+ if (currentLoadingDetails == null || currentLoadingDetails.loadingView != loadingView) {
+ val newDetails = createLoadingDetails(loadingView)
+ newDetails.animator.start()
+ loadingDetails = newDetails
+ }
+ } else {
+ loadingDetails = null
+ }
// Error
currentView.requireViewById<View>(R.id.error).visibility =
@@ -223,12 +251,17 @@
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
- val removed = chipbarAnimator.animateViewOut(innerView, onAnimationEnd)
+
+ val fullEndRunnable = Runnable {
+ loadingDetails = null
+ onAnimationEnd.run()
+ }
+ val removed = chipbarAnimator.animateViewOut(innerView, fullEndRunnable)
// If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
// run it immediately.
if (!removed) {
logger.logAnimateOutFailure()
- onAnimationEnd.run()
+ fullEndRunnable.run()
}
updateGestureListening()
@@ -269,7 +302,7 @@
}
private fun ViewGroup.getInnerView(): ViewGroup {
- return requireViewById(R.id.chipbar_inner)
+ return this.requireViewById(R.id.chipbar_inner)
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -283,8 +316,28 @@
View.GONE
}
}
+
+ private fun createLoadingDetails(loadingView: View): LoadingDetails {
+ // Ideally, we would use a <ProgressBar> view, which would automatically handle the loading
+ // spinner rotation for us. However, due to b/243983980, the ProgressBar animation
+ // unexpectedly pauses when SysUI starts another window. ObjectAnimator is a workaround that
+ // won't pause.
+ val animator =
+ ObjectAnimator.ofFloat(loadingView, View.ROTATION, 0f, 360f).apply {
+ duration = LOADING_ANIMATION_DURATION_MS
+ repeatCount = ValueAnimator.INFINITE
+ interpolator = Interpolators.LINEAR
+ }
+ return LoadingDetails(loadingView, animator)
+ }
+
+ internal data class LoadingDetails(
+ val loadingView: View,
+ val animator: ObjectAnimator,
+ )
}
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
private const val TAG = "ChipbarCoordinator"
+private const val LOADING_ANIMATION_DURATION_MS = 1000L
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 19a0866..6ef828f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -166,7 +166,8 @@
overlayAddReason = reason
- val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
+ val newRoot = SurfaceControlViewHost(context, context.display!!, wwm,
+ "UnfoldLightRevealOverlayAnimation")
val params = getLayoutParams()
val newView =
LightRevealScrim(
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 2b29885..f7c8bac 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -21,6 +21,7 @@
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
import com.android.systemui.user.ui.dialog.UserDialogModule;
import dagger.Binds;
@@ -36,6 +37,7 @@
includes = {
UserDialogModule.class,
UserRepositoryModule.class,
+ HeadlessSystemUserModeModule.class,
}
)
public abstract class UserModule {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index 0a07439..2f63f32 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -148,7 +148,7 @@
withContext(backgroundDispatcher) {
manager.getUserInfo(lastSelectedNonGuestUserHandle)
}
- if (info != null && info.isEnabled && info.supportsSwitchToByUser()) {
+ if (info != null && info.isEnabled && info.supportsSwitchTo()) {
newUserId = info.id
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt
new file mode 100644
index 0000000..756e6a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import android.os.UserManager
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface HeadlessSystemUserMode {
+
+ fun isHeadlessSystemUserMode(): Boolean
+}
+
+@SysUISingleton
+class HeadlessSystemUserModeImpl @Inject constructor() : HeadlessSystemUserMode {
+ override fun isHeadlessSystemUserMode(): Boolean {
+ return UserManager.isHeadlessSystemUserMode()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt
new file mode 100644
index 0000000..0efa2d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import dagger.Binds
+
+@dagger.Module
+interface HeadlessSystemUserModeModule {
+
+ @Binds
+ fun bindIsHeadlessSystemUserMode(impl: HeadlessSystemUserModeImpl): HeadlessSystemUserMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index c8f98c3..433642b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -89,6 +89,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val featureFlags: FeatureFlags,
private val manager: UserManager,
+ private val headlessSystemUserMode: HeadlessSystemUserMode,
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
broadcastDispatcher: BroadcastDispatcher,
@@ -571,7 +572,10 @@
actionType = action,
isRestricted = isRestricted,
isSwitchToEnabled =
- canSwitchUsers(selectedUserId) &&
+ canSwitchUsers(
+ selectedUserId = selectedUserId,
+ isAction = true,
+ ) &&
// If the user is auto-created is must not be currently resetting.
!(isGuestUserAutoCreated && isGuestUserResetting),
)
@@ -723,10 +727,32 @@
}
}
- private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
- return withContext(backgroundDispatcher) {
- manager.getUserSwitchability(UserHandle.of(selectedUserId))
- } == UserManager.SWITCHABILITY_STATUS_OK
+ private suspend fun canSwitchUsers(
+ selectedUserId: Int,
+ isAction: Boolean = false,
+ ): Boolean {
+ val isHeadlessSystemUserMode =
+ withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() }
+ // Whether menu item should be active. True if item is a user or if any user has
+ // signed in since reboot or in all cases for non-headless system user mode.
+ val isItemEnabled = !isAction || !isHeadlessSystemUserMode || isAnyUserUnlocked()
+ return isItemEnabled &&
+ withContext(backgroundDispatcher) {
+ manager.getUserSwitchability(UserHandle.of(selectedUserId))
+ } == UserManager.SWITCHABILITY_STATUS_OK
+ }
+
+ private suspend fun isAnyUserUnlocked(): Boolean {
+ return manager
+ .getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ true
+ )
+ .any { user ->
+ user.id != UserHandle.USER_SYSTEM &&
+ withContext(backgroundDispatcher) { manager.isUserUnlocked(user.userHandle) }
+ }
}
@SuppressLint("UseCompatLoadingForDrawables")
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
index b41bca0..8d32a48 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
@@ -43,11 +43,6 @@
@Override
public final void start() {
- if (mConditionSet == null || mConditionSet.isEmpty()) {
- onStart();
- return;
- }
-
mStartToken = mMonitor.addSubscription(
new Monitor.Subscription.Builder(allConditionsMet -> {
if (allConditionsMet) {
@@ -63,11 +58,6 @@
@Override
public final void onBootCompleted() {
- if (mConditionSet == null || mConditionSet.isEmpty()) {
- bootCompleted();
- return;
- }
-
mBootCompletedToken = mMonitor.addSubscription(
new Monitor.Subscription.Builder(allConditionsMet -> {
if (allConditionsMet) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 0dae918..9600fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,7 +25,6 @@
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -51,7 +50,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
@@ -60,6 +58,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -106,6 +105,7 @@
private final NotificationLockscreenUserManager mNotifUserManager;
private final CommonNotifCollection mCommonNotifCollection;
private final NotifPipeline mNotifPipeline;
+ private final NotifPipelineFlags mNotifPipelineFlags;
private final Executor mSysuiMainExecutor;
private final Bubbles.SysuiProxy mSysuiProxy;
@@ -134,6 +134,7 @@
NotifPipeline notifPipeline,
SysUiState sysUiState,
FeatureFlags featureFlags,
+ NotifPipelineFlags notifPipelineFlags,
Executor sysuiMainExecutor) {
if (bubblesOptional.isPresent()) {
return new BubblesManager(context,
@@ -152,6 +153,7 @@
notifPipeline,
sysUiState,
featureFlags,
+ notifPipelineFlags,
sysuiMainExecutor);
} else {
return null;
@@ -175,6 +177,7 @@
NotifPipeline notifPipeline,
SysUiState sysUiState,
FeatureFlags featureFlags,
+ NotifPipelineFlags notifPipelineFlags,
Executor sysuiMainExecutor) {
mContext = context;
mBubbles = bubbles;
@@ -187,6 +190,7 @@
mNotifUserManager = notifUserManager;
mCommonNotifCollection = notifCollection;
mNotifPipeline = notifPipeline;
+ mNotifPipelineFlags = notifPipelineFlags;
mSysuiMainExecutor = sysuiMainExecutor;
mBarService = statusBarService == null
@@ -618,15 +622,15 @@
}
}
- static BubbleEntry notifToBubbleEntry(NotificationEntry e) {
+ @VisibleForTesting
+ BubbleEntry notifToBubbleEntry(NotificationEntry e) {
return new BubbleEntry(e.getSbn(), e.getRanking(), isDismissableFromBubbles(e),
e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(),
e.shouldSuppressPeek());
}
- private static boolean isDismissableFromBubbles(NotificationEntry e) {
- // TODO(b/268380968): inject FlagResolver
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(ALLOW_DISMISS_ONGOING)) {
+ private boolean isDismissableFromBubbles(NotificationEntry e) {
+ if (mNotifPipelineFlags.allowDismissOngoing()) {
// Bubbles are only accessible from the unlocked state,
// so we can calculate this from the Notification flags only.
return e.isDismissableForState(/*isLocked=*/ false);
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index ed2772a..2ef3511 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -165,6 +165,12 @@
android:exported="false"
android:permission="com.android.systemui.permission.SELF"
android:excludeFromRecents="true" />
+
+ <activity
+ android:name="com.android.systemui.screenshot.appclips.AppClipsActivityTest$AppClipsActivityTestable"
+ android:exported="false"
+ android:permission="com.android.systemui.permission.SELF"
+ android:excludeFromRecents="true" />
</application>
<instrumentation android:name="android.testing.TestableInstrumentation"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index e8d50ca..badeb27 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -24,13 +24,19 @@
import android.os.PowerManager
import android.os.PowerManager.WAKE_REASON_BIOMETRIC
import android.os.UserHandle
-import android.provider.Settings
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.FakeSettings
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -41,20 +47,11 @@
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
@SmallTest
class ActiveUnlockConfigTest : SysuiTestCase() {
- private val fakeWakeUri = Uri.Builder().appendPath("wake").build()
- private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build()
- private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build()
- private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build()
- private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build()
- private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build()
- private val fakeWakeupsConsideredUnlockIntents =
- Uri.Builder().appendPath("wakeups-considered-unlock-intent").build()
-
- @Mock
- private lateinit var secureSettings: SecureSettings
+ private lateinit var secureSettings: FakeSettings
@Mock
private lateinit var contentResolver: ContentResolver
@Mock
@@ -63,33 +60,20 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mockPrintWriter: PrintWriter
@Captor
private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
private lateinit var activeUnlockConfig: ActiveUnlockConfig
+ private var currentUser: Int = 0
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE))
- .thenReturn(fakeWakeUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
- .thenReturn(fakeUnlockIntentUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
- .thenReturn(fakeBioFailUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS))
- .thenReturn(fakeFaceErrorsUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
- .thenReturn(fakeFaceAcquiredUri)
- `when`(secureSettings.getUriFor(
- Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED))
- .thenReturn(fakeUnlockIntentBioEnroll)
- `when`(secureSettings.getUriFor(
- Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
- .thenReturn(fakeWakeupsConsideredUnlockIntents)
-
+ currentUser = KeyguardUpdateMonitor.getCurrentUser()
+ secureSettings = FakeSettings()
activeUnlockConfig = ActiveUnlockConfig(
handler,
secureSettings,
@@ -105,8 +89,6 @@
@Test
fun onWakeupSettingChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -114,9 +96,8 @@
)
// WHEN unlock on wake is allowed
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
- 0, 0)).thenReturn(1)
- updateSetting(fakeWakeUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_WAKE, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE))
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
@@ -135,8 +116,6 @@
@Test
fun onUnlockIntentSettingChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -144,9 +123,8 @@
)
// WHEN unlock on biometric failed is allowed
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
- 0, 0)).thenReturn(1)
- updateSetting(fakeUnlockIntentUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -159,21 +137,19 @@
@Test
fun onBioFailSettingChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN no active unlock settings enabled and triggering unlock intent on biometric
// enrollment setting is disabled (empty string is disabled, null would use the default)
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn("")
- updateSetting(fakeUnlockIntentBioEnroll)
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser)
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
// WHEN unlock on biometric failed is allowed
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -186,17 +162,14 @@
@Test
fun faceErrorSettingsChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// WHEN face error timeout (3), allow trigger active unlock
- `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
- 0)).thenReturn("3")
- updateSetting(fakeFaceAcquiredUri)
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
// THEN active unlock triggers allowed on error TIMEOUT
assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
@@ -208,19 +181,17 @@
@Test
fun faceAcquiredSettingsChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, "1", currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
- `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
- 0)).thenReturn(
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
"${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
- "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}")
- updateSetting(fakeFaceAcquiredUri)
+ "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
+ currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
// THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
@@ -236,23 +207,23 @@
@Test
fun triggerOnUnlockIntentWhenBiometricEnrolledNone() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// GIVEN fingerprint and face are NOT enrolled
activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
// WHEN unlock intent is allowed when NO biometrics are enrolled (0)
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn("${ActiveUnlockConfig.BiometricType.NONE.intValue}")
- updateSetting(fakeUnlockIntentBioEnroll)
+
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser)
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -261,12 +232,9 @@
@Test
fun triggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// GIVEN fingerprint and face are both enrolled
activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
@@ -275,12 +243,14 @@
// WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
// are enrolled
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn(
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
"${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
- "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}")
- updateSetting(fakeUnlockIntentBioEnroll)
+ "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
+ currentUser)
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
// THEN active unlock triggers NOT allowed on unlock intent
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -305,13 +275,12 @@
@Test
fun isWakeupConsideredUnlockIntent_singleValue() {
- verifyRegisterSettingObserver()
-
// GIVEN lift is considered an unlock intent
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
- 0)).thenReturn(PowerManager.WAKE_REASON_LIFT.toString())
- updateSetting(fakeWakeupsConsideredUnlockIntents)
+ secureSettings.putIntForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ PowerManager.WAKE_REASON_LIFT,
+ currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN only WAKE_REASON_LIFT is considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -325,17 +294,15 @@
@Test
fun isWakeupConsideredUnlockIntent_multiValue() {
- verifyRegisterSettingObserver()
-
// GIVEN lift and tap are considered an unlock intent
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
- 0)).thenReturn(
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
PowerManager.WAKE_REASON_LIFT.toString() +
"|" +
- PowerManager.WAKE_REASON_TAP.toString()
+ PowerManager.WAKE_REASON_TAP.toString(),
+ currentUser
)
- updateSetting(fakeWakeupsConsideredUnlockIntents)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -354,13 +321,10 @@
@Test
fun isWakeupConsideredUnlockIntent_emptyValues() {
- verifyRegisterSettingObserver()
-
// GIVEN lift and tap are considered an unlock intent
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
- 0)).thenReturn(" ")
- updateSetting(fakeWakeupsConsideredUnlockIntents)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ",
+ currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN no wake up gestures are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -373,7 +337,23 @@
PowerManager.WAKE_REASON_UNFOLD_DEVICE))
}
+ @Test
+ fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() {
+ // GIVEN an invalid input (-1)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "-1", currentUser)
+
+ // WHEN the setting updates
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
+
+ // THEN no exception thrown
+ activeUnlockConfig.dump(mockPrintWriter, emptyArray())
+ }
+
private fun updateSetting(uri: Uri) {
+ verifyRegisterSettingObserver()
settingsObserverCaptor.value.onChange(
false,
listOf(uri),
@@ -383,13 +363,17 @@
}
private fun verifyRegisterSettingObserver() {
- verifyRegisterSettingObserver(fakeWakeUri)
- verifyRegisterSettingObserver(fakeUnlockIntentUri)
- verifyRegisterSettingObserver(fakeBioFailUri)
- verifyRegisterSettingObserver(fakeFaceErrorsUri)
- verifyRegisterSettingObserver(fakeFaceAcquiredUri)
- verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
- verifyRegisterSettingObserver(fakeWakeupsConsideredUnlockIntents)
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
+ ))
}
private fun verifyRegisterSettingObserver(uri: Uri) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 7b0d091d..0d65f12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -602,14 +602,6 @@
any(KeyguardSecurityCallback.class));
}
- @Test
- public void testReinflateViewFlipper() {
- mKeyguardSecurityContainerController.reinflateViewFlipper();
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
- any(KeyguardSecurityCallback.class));
- }
-
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4110b5a..841ec4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -85,8 +85,12 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.net.Uri;
import android.nfc.NfcAdapter;
+import android.os.BatteryManager;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -117,6 +121,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
+import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -238,9 +243,16 @@
private UiEventLogger mUiEventLogger;
@Mock
private GlobalSettings mGlobalSettings;
- private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
@Mock
private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider;
+ @Mock
+ private UsbPort mUsbPort;
+ @Mock
+ private UsbManager mUsbManager;
+ @Mock
+ private UsbPortStatus mUsbPortStatus;
+ @Mock
+ private Uri mURI;
private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
private final int mCurrentUserId = 100;
@@ -252,9 +264,6 @@
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
- @Mock
- private Uri mURI;
-
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -264,6 +273,7 @@
private MockitoSession mMockitoSession;
private StatusBarStateController.StateListener mStatusBarStateListener;
private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
+ private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
@Before
@@ -2373,6 +2383,55 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
}
+ @Test
+ public void testBatteryChangedIntent_refreshBatteryInfo() {
+ mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(mContext, getBatteryIntent());
+
+ BatteryStatus status = verifyRefreshBatteryInfo();
+ assertThat(status.incompatibleCharger.get()).isFalse();
+ assertThat(mKeyguardUpdateMonitor.mIncompatibleCharger).isFalse();
+ }
+
+ @Test
+ public void testUsbComplianceIntent_refreshBatteryInfo() {
+ Context contextSpy = getSpyContext();
+ when(contextSpy.registerReceiver(eq(null), any(IntentFilter.class)))
+ .thenReturn(getBatteryIntent());
+
+ mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(
+ contextSpy, new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED));
+
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.mIncompatibleCharger).isFalse();
+ }
+
+ @Test
+ public void testUsbComplianceIntent_refreshBatteryInfoWithIncompatibleCharger() {
+ Context contextSpy = getSpyContext();
+ setupIncompatibleCharging();
+ when(contextSpy.registerReceiver(eq(null), any(IntentFilter.class)))
+ .thenReturn(getBatteryIntent());
+
+ mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(
+ contextSpy, new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED));
+
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.mIncompatibleCharger).isTrue();
+ }
+
+ @Test
+ public void testBatteryChangedIntent_unplugDevice_resetIncompatibleCharger() {
+ mKeyguardUpdateMonitor.mIncompatibleCharger = true;
+ Intent batteryChangedIntent =
+ getBatteryIntent().putExtra(BatteryManager.EXTRA_PLUGGED, -1);
+
+ mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(mContext, batteryChangedIntent);
+
+ BatteryStatus status = verifyRefreshBatteryInfo();
+ assertThat(status.incompatibleCharger.get()).isFalse();
+ assertThat(mKeyguardUpdateMonitor.mIncompatibleCharger).isFalse();
+ }
+
private void userDeviceLockDown() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
@@ -2592,6 +2651,37 @@
return intent;
}
+ private BatteryStatus verifyRefreshBatteryInfo() {
+ mTestableLooper.processAllMessages();
+ ArgumentCaptor<BatteryStatus> captor = ArgumentCaptor.forClass(BatteryStatus.class);
+ verify(mTestCallback, atLeastOnce()).onRefreshBatteryInfo(captor.capture());
+ List<BatteryStatus> batteryStatusList = captor.getAllValues();
+ return batteryStatusList.get(batteryStatusList.size() - 1);
+ }
+
+ private void setupIncompatibleCharging() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+ when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
+ when(mUsbPortStatus.isConnected()).thenReturn(true);
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+ }
+
+ private Context getSpyContext() {
+ Context contextSpy = spy(mContext);
+ when(contextSpy.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
+ when(contextSpy.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)))
+ .thenReturn(new Intent(Intent.ACTION_BATTERY_CHANGED));
+ return contextSpy;
+ }
+
+ private Intent getBatteryIntent() {
+ return new Intent(Intent.ACTION_BATTERY_CHANGED).putExtra(
+ BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ }
+
private class TestableKeyguardUpdateMonitor extends KeyguardUpdateMonitor {
AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
new file mode 100644
index 0000000..9fcb9c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.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.keyguard
+
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NumPadAnimatorTest : SysuiTestCase() {
+ @Mock lateinit var background: GradientDrawable
+ @Mock lateinit var buttonImage: Drawable
+ private lateinit var underTest: NumPadAnimator
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = NumPadAnimator(context, background, 0, buttonImage)
+ }
+
+ @Test
+ fun testOnLayout() {
+ underTest.onLayout(100)
+ verify(background).cornerRadius = 50f
+ reset(background)
+ underTest.onLayout(100)
+ verify(background, never()).cornerRadius = anyFloat()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
new file mode 100644
index 0000000..777dd4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.accessibility.fontscaling
+
+import android.os.Handler
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.ImageView
+import android.widget.SeekBar
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for [FontScalingDialog]. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class FontScalingDialogTest : SysuiTestCase() {
+ private lateinit var fontScalingDialog: FontScalingDialog
+ private lateinit var systemSettings: SystemSettings
+ private val fontSizeValueArray: Array<String> =
+ mContext
+ .getResources()
+ .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
+
+ @Before
+ fun setUp() {
+ val mainHandler = Handler(TestableLooper.get(this).getLooper())
+ systemSettings = FakeSettings()
+ fontScalingDialog = FontScalingDialog(mContext, systemSettings as FakeSettings)
+ }
+
+ @Test
+ fun showTheDialog_seekbarIsShowingCorrectProgress() {
+ fontScalingDialog.show()
+
+ val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!!
+ val progress: Int = seekBar.getProgress()
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+
+ assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+
+ @Test
+ fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
+ fontScalingDialog.show()
+
+ val iconEnd: ImageView = fontScalingDialog.findViewById(R.id.icon_end)!!
+ val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+ fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+ val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
+
+ seekBarWithIconButtonsView.setProgress(0)
+
+ iconEnd.performClick()
+
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ assertThat(seekBar.getProgress()).isEqualTo(1)
+ assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+
+ @Test
+ fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
+ fontScalingDialog.show()
+
+ val iconStart: ImageView = fontScalingDialog.findViewById(R.id.icon_start)!!
+ val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+ fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+ val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
+
+ seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
+
+ iconStart.performClick()
+
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
+ assertThat(currentScale)
+ .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index d7aa6e0..b389558 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -19,6 +19,7 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Typeface
+import android.graphics.fonts.FontVariationAxis
import android.testing.AndroidTestingRunner
import android.text.Layout
import android.text.StaticLayout
@@ -179,4 +180,71 @@
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
+
+ @Test
+ fun testSetTextStyle_addWeight() {
+ testWeightChange("", 100, FontVariationAxis.fromFontVariationSettings("'wght' 100")!!)
+ }
+
+ @Test
+ fun testSetTextStyle_changeWeight() {
+ testWeightChange(
+ "'wght' 500",
+ 100,
+ FontVariationAxis.fromFontVariationSettings("'wght' 100")!!
+ )
+ }
+
+ @Test
+ fun testSetTextStyle_addWeightWithOtherAxis() {
+ testWeightChange(
+ "'wdth' 100",
+ 100,
+ FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
+ )
+ }
+
+ @Test
+ fun testSetTextStyle_changeWeightWithOtherAxis() {
+ testWeightChange(
+ "'wght' 500, 'wdth' 100",
+ 100,
+ FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
+ )
+ }
+
+ private fun testWeightChange(
+ initialFontVariationSettings: String,
+ weight: Int,
+ expectedFontVariationSettings: Array<FontVariationAxis>
+ ) {
+ val layout = makeLayout("Hello, World", PAINT)
+ val valueAnimator = mock(ValueAnimator::class.java)
+ val textInterpolator = mock(TextInterpolator::class.java)
+ val paint =
+ TextPaint().apply {
+ typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
+ fontVariationSettings = initialFontVariationSettings
+ }
+ `when`(textInterpolator.targetPaint).thenReturn(paint)
+
+ val textAnimator =
+ TextAnimator(layout, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
+ textAnimator.setTextStyle(weight = weight, animate = false)
+
+ val resultFontVariationList =
+ FontVariationAxis.fromFontVariationSettings(
+ textInterpolator.targetPaint.fontVariationSettings
+ )
+ expectedFontVariationSettings.forEach { expectedAxis ->
+ val resultAxis = resultFontVariationList?.filter { it.tag == expectedAxis.tag }?.get(0)
+ assertThat(resultAxis).isNotNull()
+ if (resultAxis != null) {
+ assertThat(resultAxis.styleValue).isEqualTo(expectedAxis.styleValue)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index b9db9c4..b3329eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -32,6 +32,7 @@
import com.android.settingslib.dream.DreamBackend;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Monitor;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -67,7 +68,6 @@
private ComplicationTypesUpdater mController;
- @Mock
private Monitor mMonitor;
@Before
@@ -75,6 +75,7 @@
MockitoAnnotations.initMocks(this);
when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
+ mMonitor = SelfExecutingMonitor.createInstance();
mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
mSecureSettings, mDreamOverlayStateController, mMonitor);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
index 52aaea1..f6662d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Monitor;
@@ -70,13 +71,13 @@
@Mock
private ComplicationLayoutParams mLayoutParams;
- @Mock
private Monitor mMonitor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mDreamClockTimeViewHolderProvider.get()).thenReturn(mDreamClockTimeViewHolder);
+ mMonitor = SelfExecutingMonitor.createInstance();
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 8534d4f..3312c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.view.LaunchableImageView;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.controller.StructureInfo;
@@ -102,7 +103,6 @@
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
- @Mock
private Monitor mMonitor;
@Before
@@ -116,6 +116,8 @@
Optional.of(mControlsListingController));
when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
+
+ mMonitor = SelfExecutingMonitor.createInstance();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index 77ca958..ef62abf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
@@ -64,7 +65,6 @@
@Mock
private View mBcSmartspaceView;
- @Mock
private Monitor mMonitor;
private final Set<Condition> mPreconditions = new HashSet<>();
@@ -72,6 +72,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mMonitor = SelfExecutingMonitor.createInstance();
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index db18ba6..5bb8367 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -18,15 +18,19 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
+import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.pm.PackageManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -44,21 +48,28 @@
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
@Mock private lateinit var context: Context
@Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: CameraQuickAffordanceConfig
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- setLaunchable(true)
+ setLaunchable()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
underTest =
CameraQuickAffordanceConfig(
context,
packageManager,
- ) {
- cameraGestureHelper
- }
+ { cameraGestureHelper },
+ userTracker,
+ devicePolicyManager,
+ testDispatcher,
+ )
}
@Test
@@ -73,23 +84,57 @@
}
@Test
- fun `getPickerScreenState - default when launchable`() = runTest {
- setLaunchable(true)
+ fun `getPickerScreenState - default when launchable`() =
+ testScope.runTest {
+ setLaunchable(true)
- Truth.assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
- }
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
@Test
- fun `getPickerScreenState - unavailable when not launchable`() = runTest {
- setLaunchable(false)
+ fun `getPickerScreenState - unavailable when camera app not installed`() =
+ testScope.runTest {
+ setLaunchable(isCameraAppInstalled = false)
- Truth.assertThat(underTest.getPickerScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
- }
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
- private fun setLaunchable(isLaunchable: Boolean) {
+ @Test
+ fun `getPickerScreenState - unavailable when camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isCameraDisabledByDeviceAdmin = true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when secure camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isSecureCameraDisabledByDeviceAdmin = true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(
+ isCameraAppInstalled: Boolean = true,
+ isCameraDisabledByDeviceAdmin: Boolean = false,
+ isSecureCameraDisabledByDeviceAdmin: Boolean = false,
+ ) {
whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
- .thenReturn(isLaunchable)
+ .thenReturn(isCameraAppInstalled)
+ whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId))
+ .thenReturn(isCameraDisabledByDeviceAdmin)
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+ .thenReturn(
+ if (isSecureCameraDisabledByDeviceAdmin) {
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
+ } else {
+ 0
+ }
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
index 5bd86bd..f1b9c5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.app.admin.DevicePolicyManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.ActivityIntentHelper
@@ -24,11 +25,14 @@
import com.android.systemui.camera.CameraIntentsWrapper
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -44,59 +48,94 @@
class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: VideoCameraQuickAffordanceConfig
+ private lateinit var userTracker: UserTracker
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ userTracker = FakeUserTracker()
underTest =
VideoCameraQuickAffordanceConfig(
context = context,
cameraIntents = CameraIntentsWrapper(context),
activityIntentHelper = activityIntentHelper,
- userTracker = FakeUserTracker(),
+ userTracker = userTracker,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
}
@Test
- fun `lockScreenState - visible when launchable`() = runTest {
- setLaunchable(true)
+ fun `lockScreenState - visible when launchable`() =
+ testScope.runTest {
+ setLaunchable()
- val lockScreenState = collectLastValue(underTest.lockScreenState)
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
- assertThat(lockScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
- }
+ assertThat(lockScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ }
@Test
- fun `lockScreenState - hidden when not launchable`() = runTest {
- setLaunchable(false)
+ fun `lockScreenState - hidden when app not installed on device`() =
+ testScope.runTest {
+ setLaunchable(isVideoCameraAppInstalled = false)
- val lockScreenState = collectLastValue(underTest.lockScreenState)
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
- assertThat(lockScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- }
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
- fun `getPickerScreenState - default when launchable`() = runTest {
- setLaunchable(true)
+ fun `lockScreenState - hidden when camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isCameraDisabledByAdmin = true)
- assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
- }
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
- fun `getPickerScreenState - unavailable when not launchable`() = runTest {
- setLaunchable(false)
+ fun `getPickerScreenState - default when launchable`() =
+ testScope.runTest {
+ setLaunchable()
- assertThat(underTest.getPickerScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
- }
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
- private fun setLaunchable(isLaunchable: Boolean) {
+ @Test
+ fun `getPickerScreenState - unavailable when app not installed on device`() =
+ testScope.runTest {
+ setLaunchable(isVideoCameraAppInstalled = false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isCameraDisabledByAdmin = true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(
+ isVideoCameraAppInstalled: Boolean = true,
+ isCameraDisabledByAdmin: Boolean = false,
+ ) {
whenever(
activityIntentHelper.getTargetActivityInfo(
any(),
@@ -105,11 +144,13 @@
)
)
.thenReturn(
- if (isLaunchable) {
+ if (isVideoCameraAppInstalled) {
mock()
} else {
null
}
)
+ whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId))
+ .thenReturn(isCameraDisabledByAdmin)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 997198e..a72634b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -61,7 +61,6 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -69,6 +68,8 @@
import org.mockito.Mock
import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -107,7 +108,6 @@
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
- @Captor lateinit var newConfig: ArgumentCaptor<Configuration>
@Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
@Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -150,7 +150,6 @@
MediaPlayerData.clear()
}
- @Ignore("b/253229241")
@Test
fun testPlayerOrdering() {
// Test values: key, data, last active time
@@ -327,7 +326,6 @@
}
}
- @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_prioritized() {
testPlayerOrdering()
@@ -335,7 +333,7 @@
// If smartspace is prioritized
MediaPlayerData.addMediaRecommendation(
SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
true,
clock
@@ -345,7 +343,6 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
- @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
testPlayerOrdering()
@@ -362,7 +359,6 @@
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
}
- @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_notPrioritized() {
testPlayerOrdering()
@@ -370,7 +366,7 @@
// If smartspace is not prioritized
MediaPlayerData.addMediaRecommendation(
SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
false,
clock
@@ -381,7 +377,6 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
}
- @Ignore("b/253229241")
@Test
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
testPlayerOrdering()
@@ -419,7 +414,6 @@
)
}
- @Ignore("b/253229241")
@Test
fun testSwipeDismiss_logged() {
mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -427,7 +421,6 @@
verify(logger).logSwipeDismiss()
}
- @Ignore("b/253229241")
@Test
fun testSettingsButton_logged() {
mediaCarouselController.settingsButton.callOnClick()
@@ -435,18 +428,16 @@
verify(logger).logCarouselSettings()
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeQs_logged() {
mediaCarouselController.onDesiredLocationChanged(
- MediaHierarchyManager.LOCATION_QS,
+ LOCATION_QS,
mediaHostState,
animate = false
)
- verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+ verify(logger).logCarouselPosition(LOCATION_QS)
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeQqs_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -457,7 +448,6 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeLockscreen_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -468,7 +458,6 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeDream_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -479,7 +468,6 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
}
- @Ignore("b/253229241")
@Test
fun testRecommendationRemoved_logged() {
val packageName = "smartspace package"
@@ -493,7 +481,6 @@
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
- @Ignore("b/253229241")
@Test
fun testMediaLoaded_ScrollToActivePlayer() {
listener.value.onMediaDataLoaded(
@@ -551,7 +538,6 @@
)
}
- @Ignore("b/253229241")
@Test
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
listener.value.onSmartspaceMediaDataLoaded(
@@ -595,7 +581,6 @@
assertEquals(playerIndex, 0)
}
- @Ignore("b/253229241")
@Test
fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
var result = false
@@ -607,7 +592,6 @@
assertEquals(true, result)
}
- @Ignore("b/253229241")
@Test
fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
var result = false
@@ -621,7 +605,6 @@
assertEquals(true, result)
}
- @Ignore("b/253229241")
@Test
fun testGetCurrentVisibleMediaContentIntent() {
val clickIntent1 = mock(PendingIntent::class.java)
@@ -668,7 +651,6 @@
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
}
- @Ignore("b/253229241")
@Test
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
val delta = 0.0001F
@@ -690,7 +672,6 @@
verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta }
}
- @Ignore("b/253229241")
@Test
fun testOnConfigChanged_playersAreAddedBack() {
listener.value.onMediaDataLoaded(
@@ -716,7 +697,7 @@
val playersSize = MediaPlayerData.players().size
- configListener.value.onConfigChanged(capture(newConfig))
+ configListener.value.onConfigChanged(Configuration())
assertEquals(playersSize, MediaPlayerData.players().size)
assertEquals(
@@ -796,4 +777,59 @@
job.cancel()
}
+
+ @Test
+ fun testInvisibleToUserAndExpanded_playersNotListening() {
+ // Add players to carousel.
+ testPlayerOrdering()
+
+ // Make the carousel visible to user in expanded layout.
+ mediaCarouselController.currentlyExpanded = true
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to true.
+ verify(panel, times(MediaPlayerData.players().size)).listening = true
+
+ // Make the carousel invisible to user.
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = false
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to false.
+ verify(panel, times(MediaPlayerData.players().size)).listening = false
+ }
+
+ @Test
+ fun testVisibleToUserAndExpanded_playersListening() {
+ // Add players to carousel.
+ testPlayerOrdering()
+
+ // Make the carousel visible to user in expanded layout.
+ mediaCarouselController.currentlyExpanded = true
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to true.
+ verify(panel, times(MediaPlayerData.players().size)).listening = true
+ }
+
+ @Test
+ fun testUMOCollapsed_playersNotListening() {
+ // Add players to carousel.
+ testPlayerOrdering()
+
+ // Make the carousel in collapsed layout.
+ mediaCarouselController.currentlyExpanded = false
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to false.
+ verify(panel, times(MediaPlayerData.players().size)).listening = false
+
+ // Make the carousel visible to user.
+ reset(panel)
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true
+
+ // Verify that seekbar listening attribute in media control panel is set to false.
+ verify(panel, times(MediaPlayerData.players().size)).listening = false
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt
new file mode 100644
index 0000000..57abae0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.qs.tiles.dialog
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tiles.FontScalingTile
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class FontScalingTileTest : SysuiTestCase() {
+ @Mock private lateinit var qsHost: QSTileHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var fontScalingTile: FontScalingTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ `when`(qsHost.getContext()).thenReturn(mContext)
+ fontScalingTile =
+ FontScalingTile(
+ qsHost,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ dialogLaunchAnimator,
+ FakeSettings()
+ )
+ fontScalingTile.initialize()
+ }
+
+ @Test
+ fun isNotAvailable_whenNotSupportedDevice_returnsFalse() {
+ val isAvailable = fontScalingTile.isAvailable()
+
+ assertThat(isAvailable).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
new file mode 100644
index 0000000..d828e51
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.screenshot.appclips;
+
+import static android.app.Activity.RESULT_OK;
+
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.testing.AndroidTestingRunner;
+import android.widget.ImageView;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.intercepting.SingleActivityFactory;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.screenshot.AppClipsActivity;
+import com.android.systemui.screenshot.AppClipsTrampolineActivity;
+import com.android.systemui.screenshot.AppClipsViewModel;
+import com.android.systemui.settings.UserTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiConsumer;
+
+
+@RunWith(AndroidTestingRunner.class)
+public final class AppClipsActivityTest extends SysuiTestCase {
+
+ private static final int TEST_UID = 42;
+ private static final int TEST_USER_ID = 43;
+ private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+ private static final String TEST_URI_STRING = "www.test-uri.com";
+ private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
+ private static final BiConsumer<Integer, Bundle> FAKE_CONSUMER = (unUsed1, unUsed2) -> {};
+ private static final String TEST_CALLING_PACKAGE = "test-calling-package";
+
+ @Mock
+ private AppClipsViewModel.Factory mViewModelFactory;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private UserTracker mUserTracker;
+ @Mock
+ private UiEventLogger mUiEventLogger;
+ @Mock
+ private AppClipsViewModel mViewModel;
+
+ private MutableLiveData<Bitmap> mScreenshotLiveData;
+ private MutableLiveData<Uri> mResultLiveData;
+ private AppClipsActivity mActivity;
+
+ // Using the deprecated ActivityTestRule and SingleActivityFactory to help with injecting mocks.
+ private final SingleActivityFactory<AppClipsActivityTestable> mFactory =
+ new SingleActivityFactory<>(AppClipsActivityTestable.class) {
+ @Override
+ protected AppClipsActivityTestable create(Intent unUsed) {
+ return new AppClipsActivityTestable(mViewModelFactory, mPackageManager,
+ mUserTracker, mUiEventLogger);
+ }
+ };
+
+ @Rule
+ public final ActivityTestRule<AppClipsActivityTestable> mActivityRule =
+ new ActivityTestRule<>(mFactory, false, false);
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+
+ mScreenshotLiveData = new MutableLiveData<>();
+ mResultLiveData = new MutableLiveData<>();
+ MutableLiveData<Integer> errorLiveData = new MutableLiveData<>();
+
+ when(mViewModelFactory.create(any(Class.class))).thenReturn(mViewModel);
+ when(mViewModel.getScreenshot()).thenReturn(mScreenshotLiveData);
+ when(mViewModel.getResultLiveData()).thenReturn(mResultLiveData);
+ when(mViewModel.getErrorLiveData()).thenReturn(errorLiveData);
+ when(mUserTracker.getUserId()).thenReturn(TEST_USER_ID);
+
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = TEST_UID;
+ when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE),
+ any(ApplicationInfoFlags.class), eq(TEST_USER_ID))).thenReturn(applicationInfo);
+
+ doAnswer(invocation -> {
+ runOnMainThread(() -> mScreenshotLiveData.setValue(TEST_BITMAP));
+ return null;
+ }).when(mViewModel).performScreenshot();
+ doAnswer(invocation -> {
+ runOnMainThread(() -> mResultLiveData.setValue(TEST_URI));
+ return null;
+ }).when(mViewModel).saveScreenshotThenFinish(any(Drawable.class), any(Rect.class));
+ }
+
+ @After
+ public void tearDown() {
+ mActivityRule.finishActivity();
+ }
+
+ @Test
+ public void appClipsLaunched_screenshotDisplayed() {
+ launchActivity();
+
+ assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull();
+ }
+
+ @Test
+ public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() {
+ ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
+ assertThat(resultCode).isEqualTo(RESULT_OK);
+ assertThat(
+ data.getParcelable(AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI, Uri.class))
+ .isEqualTo(TEST_URI);
+ assertThat(data.getInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE))
+ .isEqualTo(Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+ });
+
+ launchActivity(resultReceiver);
+ runOnMainThread(() -> mActivity.findViewById(R.id.save).performClick());
+ waitForIdleSync();
+
+ assertThat(mActivity.isFinishing()).isTrue();
+ verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_ACCEPTED, TEST_UID, TEST_CALLING_PACKAGE);
+ }
+
+ @Test
+ public void screenshotDisplayed_userDeclined() {
+ ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
+ assertThat(resultCode).isEqualTo(RESULT_OK);
+ assertThat(data.getInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE))
+ .isEqualTo(Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+ assertThat(data.keySet().contains(AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI))
+ .isFalse();
+ });
+
+ launchActivity(resultReceiver);
+ runOnMainThread(() -> mActivity.findViewById(R.id.cancel).performClick());
+ waitForIdleSync();
+
+ assertThat(mActivity.isFinishing()).isTrue();
+ verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_CANCELLED, TEST_UID, TEST_CALLING_PACKAGE);
+ }
+
+ private void launchActivity() {
+ launchActivity(createResultReceiver(FAKE_CONSUMER));
+ }
+
+ private void launchActivity(ResultReceiver resultReceiver) {
+ Intent intent = new Intent()
+ .putExtra(AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER, resultReceiver)
+ .putExtra(AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME,
+ TEST_CALLING_PACKAGE);
+
+ mActivity = mActivityRule.launchActivity(intent);
+ waitForIdleSync();
+ }
+
+ private ResultReceiver createResultReceiver(
+ BiConsumer<Integer, Bundle> resultReceiverConsumer) {
+ ResultReceiver testReceiver = new ResultReceiver(mContext.getMainThreadHandler()) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ resultReceiverConsumer.accept(resultCode, resultData);
+ }
+ };
+
+ Parcel parcel = Parcel.obtain();
+ testReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ testReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return testReceiver;
+ }
+
+ private void runOnMainThread(Runnable runnable) {
+ mContext.getMainExecutor().execute(runnable);
+ }
+
+ public static class AppClipsActivityTestable extends AppClipsActivity {
+
+ public AppClipsActivityTestable(AppClipsViewModel.Factory viewModelFactory,
+ PackageManager packageManager,
+ UserTracker userTracker,
+ UiEventLogger uiEventLogger) {
+ super(viewModelFactory, packageManager, userTracker, uiEventLogger);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
index 6e8f5fe..ab321f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
@@ -76,7 +76,7 @@
}
@Test
- public void bubblesPresent_screenshotFailed_ShouldReturnNull() throws RemoteException {
+ public void bubblesPresent_screenshotFailed_shouldReturnNull() throws RemoteException {
when(mBubblesOptional.isEmpty()).thenReturn(false);
when(mBubblesOptional.get()).thenReturn(mBubbles);
when(mBubbles.getScreenshotExcludingBubble(DEFAULT_DISPLAY)).thenReturn(mScreenshotSync);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index 295d127..e40c49b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -25,23 +25,25 @@
import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -51,12 +53,15 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.intercepting.SingleActivityFactory;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.screenshot.AppClipsTrampolineActivity;
+import com.android.systemui.screenshot.ScreenshotEvent;
+import com.android.systemui.settings.UserTracker;
import com.android.wm.shell.bubbles.Bubbles;
import org.junit.After;
@@ -74,7 +79,9 @@
private static final String TEST_URI_STRING = "www.test-uri.com";
private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
- private static final int TIME_OUT = 5000;
+ private static final int TEST_UID = 42;
+ private static final int TEST_USER_ID = 43;
+ private static final String TEST_CALLING_PACKAGE = "test-calling-package";
@Mock
private DevicePolicyManager mDevicePolicyManager;
@@ -86,6 +93,12 @@
private Bubbles mBubbles;
@Mock
private NoteTaskController mNoteTaskController;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private UserTracker mUserTracker;
+ @Mock
+ private UiEventLogger mUiEventLogger;
@Main
private Handler mMainHandler;
@@ -96,7 +109,8 @@
@Override
protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
- mFeatureFlags, mOptionalBubbles, mNoteTaskController, mMainHandler);
+ mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
+ mUserTracker, mUiEventLogger, mMainHandler);
}
};
@@ -104,41 +118,36 @@
public final ActivityTestRule<AppClipsTrampolineActivityTestable> mActivityRule =
new ActivityTestRule<>(mFactory, false, false);
- private Context mContext;
private Intent mActivityIntent;
private ComponentName mExpectedComponentName;
- private Intent mKillAppClipsActivityBroadcast;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = getContext();
mMainHandler = mContext.getMainThreadHandler();
mActivityIntent = new Intent(mContext, AppClipsTrampolineActivityTestable.class);
mExpectedComponentName = ComponentName.unflattenFromString(
mContext.getString(
R.string.config_screenshotAppClipsActivityComponent));
- mKillAppClipsActivityBroadcast = new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
- .setComponent(mExpectedComponentName)
- .setPackage(mExpectedComponentName.getPackageName());
}
@After
public void tearDown() {
- mContext.sendBroadcast(mKillAppClipsActivityBroadcast, PERMISSION_SELF);
mActivityRule.finishActivity();
}
@Test
- public void configComponentName_shouldResolve() {
+ public void appClipsActivityConfig_shouldBeConfigured() {
// Verify component name is setup - has package and class name.
assertThat(mExpectedComponentName).isNotNull();
assertThat(mExpectedComponentName.getPackageName()).isNotEmpty();
assertThat(mExpectedComponentName.getClassName()).isNotEmpty();
+ }
- // Verify an intent when launched with above component resolves to the same component to
- // confirm that component from above is available in framework.
+ @Test
+ public void configComponentName_shouldResolve() {
+ // Verify an intent when launched with configured component resolves to activity.
Intent appClipsActivityIntent = new Intent();
appClipsActivityIntent.setComponent(mExpectedComponentName);
ResolveInfo resolveInfo = getContext().getPackageManager().resolveActivity(
@@ -205,7 +214,8 @@
}
@Test
- public void startAppClipsActivity_userCanceled_shouldReturnUserCanceled() {
+ public void startAppClipsActivity_userCanceled_shouldReturnUserCanceled()
+ throws NameNotFoundException {
mockToSatisfyAllPrerequisites();
AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
@@ -224,7 +234,8 @@
}
@Test
- public void startAppClipsActivity_shouldReturnSuccess() {
+ public void startAppClipsActivity_shouldReturnSuccess()
+ throws NameNotFoundException {
mockToSatisfyAllPrerequisites();
AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
@@ -243,12 +254,31 @@
assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
}
- private void mockToSatisfyAllPrerequisites() {
+ @Test
+ public void startAppClipsActivity_shouldLogUiEvent()
+ throws NameNotFoundException {
+ mockToSatisfyAllPrerequisites();
+
+ mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ verify(mUiEventLogger).log(ScreenshotEvent.SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID,
+ TEST_CALLING_PACKAGE);
+ }
+
+ private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
when(mOptionalBubbles.get()).thenReturn(mBubbles);
when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+ when(mUserTracker.getUserId()).thenReturn(TEST_USER_ID);
+
+ ApplicationInfo testApplicationInfo = new ApplicationInfo();
+ testApplicationInfo.uid = TEST_UID;
+ when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE),
+ any(ApplicationInfoFlags.class),
+ eq(TEST_USER_ID))).thenReturn(testApplicationInfo);
}
public static final class AppClipsTrampolineActivityTestable extends
@@ -258,8 +288,22 @@
FeatureFlags flags,
Optional<Bubbles> optionalBubbles,
NoteTaskController noteTaskController,
+ PackageManager packageManager,
+ UserTracker userTracker,
+ UiEventLogger uiEventLogger,
@Main Handler mainHandler) {
- super(devicePolicyManager, flags, optionalBubbles, noteTaskController, mainHandler);
+ super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
+ userTracker, uiEventLogger, mainHandler);
+ }
+
+ @Override
+ public String getCallingPackage() {
+ return TEST_CALLING_PACKAGE;
+ }
+
+ @Override
+ public void startActivity(Intent unUsed) {
+ // Ignore this intent to avoid App Clips screenshot editing activity from starting.
}
}
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 9eccbb6..aa1636d 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
@@ -249,6 +249,21 @@
}
@Test
+ public void addCallback_preCondition_noConditions_reportAllConditionsMet() {
+ final Monitor
+ monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1)));
+ final Monitor.Callback callback = mock(
+ Monitor.Callback.class);
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
+ mExecutor.runAllReady();
+ verify(callback, never()).onConditionsChanged(true);
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ }
+
+ @Test
public void removeCallback_noFailureOnDoubleRemove() {
final Condition condition = mock(
Condition.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
new file mode 100644
index 0000000..109f185
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardShortcutListSearchTest extends SysuiTestCase {
+
+ @Rule public MockitoRule mockito = MockitoJUnit.rule();
+
+ private static int DEVICE_ID = 1;
+ private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
+
+ @Mock private BottomSheetDialog mBottomSheetDialog;
+ @Mock WindowManager mWindowManager;
+
+ @Before
+ public void setUp() {
+ mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager);
+ mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
+ mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog;
+ mKeyboardShortcutListSearch.mContext = mContext;
+ }
+
+ @Test
+ public void toggle_isShowingTrue_instanceShouldBeNull() {
+ when(mBottomSheetDialog.isShowing()).thenReturn(true);
+
+ mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
+
+ assertThat(mKeyboardShortcutListSearch.sInstance).isNull();
+ }
+
+ @Test
+ public void toggle_isShowingFalse_showKeyboardShortcuts() {
+ when(mBottomSheetDialog.isShowing()).thenReturn(false);
+
+ mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
+
+ verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
new file mode 100644
index 0000000..bea2cfb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
+
+ @Rule public MockitoRule mockito = MockitoJUnit.rule();
+
+ private KeyboardShortcutsReceiver mKeyboardShortcutsReceiver;
+ private Intent mIntent;
+ private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+
+ @Mock private KeyboardShortcuts mKeyboardShortcuts;
+ @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
+
+ @Before
+ public void setUp() {
+ mIntent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+ mKeyboardShortcuts.mContext = mContext;
+ mKeyboardShortcutListSearch.mContext = mContext;
+ KeyboardShortcuts.sInstance = mKeyboardShortcuts;
+ KeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
+ }
+
+ @Test
+ public void onReceive_whenFlagOffDeviceIsTablet_showKeyboardShortcuts() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Utilities.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
+ when(Utilities.isTablet(mContext)).thenReturn(true);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+
+ verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt());
+ verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt());
+ mockitoSession.finishMocking();
+ }
+
+ @Test
+ public void onReceive_whenFlagOffDeviceIsNotTablet_showKeyboardShortcuts() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Utilities.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
+ when(Utilities.isTablet(mContext)).thenReturn(false);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+
+ verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt());
+ verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt());
+ mockitoSession.finishMocking();
+ }
+
+ @Test
+ public void onReceive_whenFlagOnDeviceIsTablet_showKeyboardShortcutListSearch() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Utilities.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
+ when(Utilities.isTablet(mContext)).thenReturn(true);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+
+ verify(mKeyboardShortcuts, never()).showKeyboardShortcuts(anyInt());
+ verify(mKeyboardShortcutListSearch).showKeyboardShortcuts(anyInt());
+ mockitoSession.finishMocking();
+ }
+
+ @Test
+ public void onReceive_whenFlagOnDeviceIsNotTablet_showKeyboardShortcuts() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Utilities.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
+ when(Utilities.isTablet(mContext)).thenReturn(false);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+
+ verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt());
+ verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt());
+ mockitoSession.finishMocking();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
new file mode 100644
index 0000000..ea822aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Dialog;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardShortcutsTest extends SysuiTestCase {
+
+ @Rule public MockitoRule mockito = MockitoJUnit.rule();
+
+ private static int DEVICE_ID = 1;
+ private KeyboardShortcuts mKeyboardShortcuts;
+
+ @Mock private Dialog mDialog;
+ @Mock WindowManager mWindowManager;
+
+ @Before
+ public void setUp() {
+ mKeyboardShortcuts = new KeyboardShortcuts(mContext, mWindowManager);
+ mKeyboardShortcuts.sInstance = mKeyboardShortcuts;
+ mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog;
+ mKeyboardShortcuts.mContext = mContext;
+ }
+
+ @Test
+ public void toggle_isShowingTrue_instanceShouldBeNull() {
+ when(mDialog.isShowing()).thenReturn(true);
+
+ mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
+
+ assertThat(mKeyboardShortcuts.sInstance).isNull();
+ }
+
+ @Test
+ public void toggle_isShowingFalse_showKeyboardShortcuts() {
+ when(mDialog.isShowing()).thenReturn(false);
+
+ mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
+
+ verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
index 55a79da..ed24947 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
@@ -19,10 +19,9 @@
import android.app.Notification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -48,15 +47,14 @@
private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
private val keyguardStateController: KeyguardStateController = mock()
private val pipeline: NotifPipeline = mock()
- private val flagResolver: SystemUiSystemPropertiesFlags.FlagResolver = mock()
+ private val flags: NotifPipelineFlags = mock()
private val dumpManager: DumpManager = mock()
@Before
fun setUp() {
- setTestFlagResolver(flagResolver)
- whenever(flagResolver.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)).thenReturn(true)
+ whenever(flags.allowDismissOngoing()).thenReturn(true)
- dismissibilityProvider = NotificationDismissibilityProviderImpl(dumpManager)
+ dismissibilityProvider = NotificationDismissibilityProviderImpl(flags, dumpManager)
coordinator = DismissibilityCoordinator(keyguardStateController, dismissibilityProvider)
coordinator.attach(pipeline)
onBeforeRenderListListener = withArgCaptor {
@@ -314,7 +312,7 @@
@Test
fun testFeatureToggleOffNonDismissibleEntry() {
- whenever(flagResolver.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)).thenReturn(false)
+ whenever(flags.allowDismissOngoing()).thenReturn(false)
val entry =
NotificationEntryBuilder()
.setTag("entry")
@@ -331,7 +329,7 @@
@Test
fun testFeatureToggleOffOngoingNotifWhenPhoneIsLocked() {
- whenever(flagResolver.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)).thenReturn(false)
+ whenever(flags.allowDismissOngoing()).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
val entry =
NotificationEntryBuilder()
@@ -349,7 +347,7 @@
@Test
fun testFeatureToggleOffOngoingNotifWhenPhoneIsUnLocked() {
- whenever(flagResolver.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)).thenReturn(false)
+ whenever(flags.allowDismissOngoing()).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(true)
val entry =
NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 831d07f..07d0dbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -741,7 +741,7 @@
}
@Test
- public void testShouldFullScreen_snoozed_occluding_withStrictRules() throws Exception {
+ public void testShouldNotFullScreen_snoozed_occluding_withStrictRules() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -753,16 +753,41 @@
when(mKeyguardStateController.isOccluded()).thenReturn(true);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
- .isEqualTo(FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED);
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
- .isTrue();
- verify(mLogger, never()).logNoFullscreen(any(), any());
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+ verify(mLogger, never()).logFullscreen(any(), any());
}
@Test
- public void testShouldFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
+ public void testShouldHeadsUp_snoozed_occluding_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger).logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ verify(mLogger, never()).logHeadsUp(any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
+ }
+
+ @Test
+ public void testShouldNotFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -774,12 +799,37 @@
when(mKeyguardStateController.isOccluded()).thenReturn(false);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
- .isEqualTo(FullScreenIntentDecision.FSI_LOCKED_SHADE);
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
- .isTrue();
- verify(mLogger, never()).logNoFullscreen(any(), any());
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_snoozed_lockedShade_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger).logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ verify(mLogger, never()).logHeadsUp(any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
@Test
@@ -795,21 +845,41 @@
when(mKeyguardStateController.isOccluded()).thenReturn(false);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
- .isEqualTo(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD);
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger, never()).logNoFullscreen(any(), any());
- verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_snoozed_unlocked_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger).logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ verify(mLogger, never()).logHeadsUp(any());
assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
assertThat(fakeUiEvent.eventId).isEqualTo(
- NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD.getId());
+ NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI.getId());
assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
+ /* TODO: Verify the FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD UiEvent some other way. */
+
/**
* Bubbles can happen.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 0605398..8b0d4ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -327,6 +327,8 @@
// CentralSurfacesImpl's runtime flag check fails if the flag is absent.
// This value is unused, because test manifest is opted in.
mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
+ // Set default value to avoid IllegalStateException.
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
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 673e559..3853b99 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
@@ -896,21 +896,31 @@
// Subscription 1
private const val SUB_1_ID = 1
+ private val GROUP_1 = ParcelUuid(UUID.randomUUID())
private val SUB_1 =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_1_ID)
- whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+ whenever(it.groupUuid).thenReturn(GROUP_1)
}
- private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+ private val MODEL_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ groupUuid = GROUP_1,
+ )
// Subscription 2
private const val SUB_2_ID = 2
+ private val GROUP_2 = ParcelUuid(UUID.randomUUID())
private val SUB_2 =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_2_ID)
- whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+ whenever(it.groupUuid).thenReturn(GROUP_2)
}
- private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+ private val MODEL_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ groupUuid = GROUP_2,
+ )
// Subs 3 and 4 are considered to be in the same group ------------------------------------
private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
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 f8a9783..bbca001 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.os.ParcelUuid
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
@@ -34,6 +35,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -104,6 +106,21 @@
job.cancel()
}
+ // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
+ @Test
+ fun filteredSubscriptions_moreThanTwo_doesNotFilter() =
+ testScope.runTest {
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+
+ job.cancel()
+ }
+
@Test
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
testScope.runTest {
@@ -118,10 +135,50 @@
}
@Test
- fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+ fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() =
testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() =
+ testScope.runTest {
+ val (sub1, sub2) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
+ opportunistic = Pair(true, true),
+ grouped = false,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub2))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(sub1, sub2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() =
+ testScope.runTest {
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -129,15 +186,21 @@
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false
- assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+ assertThat(latest).isEqualTo(listOf(sub3))
job.cancel()
}
@Test
- fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() =
testScope.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -146,15 +209,21 @@
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false
- assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+ assertThat(latest).isEqualTo(listOf(sub4))
job.cancel()
}
@Test
- fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() =
testScope.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
@@ -164,15 +233,21 @@
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
- assertThat(latest).isEqualTo(listOf(SUB_1))
+ assertThat(latest).isEqualTo(listOf(sub1))
job.cancel()
}
@Test
- fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
testScope.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
@@ -182,7 +257,7 @@
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
- assertThat(latest).isEqualTo(listOf(SUB_1))
+ assertThat(latest).isEqualTo(listOf(sub1))
job.cancel()
}
@@ -642,6 +717,33 @@
job.cancel()
}
+ /**
+ * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
+ * flow.
+ */
+ private fun createSubscriptionPair(
+ subscriptionIds: Pair<Int, Int>,
+ opportunistic: Pair<Boolean, Boolean> = Pair(false, false),
+ grouped: Boolean = false,
+ ): Pair<SubscriptionModel, SubscriptionModel> {
+ val groupUuid = if (grouped) ParcelUuid(UUID.randomUUID()) else null
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.first,
+ isOpportunistic = opportunistic.first,
+ groupUuid = groupUuid,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.second,
+ isOpportunistic = opportunistic.second,
+ groupUuid = groupUuid,
+ )
+
+ return Pair(sub1, sub2)
+ }
+
companion object {
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
@@ -655,11 +757,21 @@
private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
private const val SUB_3_ID = 3
- private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
+ private val SUB_3_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_3_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ )
private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
private const val SUB_4_ID = 4
- private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
+ private val SUB_4_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_4_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ )
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index 56203d9..48a2e09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -19,10 +19,16 @@
import android.bluetooth.BluetoothDevice
import android.hardware.BatteryState
import android.hardware.input.InputManager
+import android.hardware.input.InputSettings
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.InputDevice
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.ExtendedMockito.times
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
@@ -30,18 +36,17 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import java.util.concurrent.Executor
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -67,11 +72,17 @@
@Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback
+ private lateinit var mockitoSession: StaticMockitoSession
private lateinit var stylusManager: StylusManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ mockitoSession =
+ mockitoSession()
+ .mockStatic(InputSettings::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
whenever(handler.post(any())).thenAnswer {
(it.arguments[0] as Runnable).run()
@@ -96,23 +107,32 @@
whenever(stylusDevice.bluetoothAddress).thenReturn(null)
whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+ whenever(stylusDevice.batteryState).thenReturn(batteryState)
+ whenever(batteryState.capacity).thenReturn(0.5f)
+
whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
- whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(false)
whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)
+ whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(false)
+
stylusManager.startListener()
stylusManager.registerCallback(stylusCallback)
stylusManager.registerBatteryCallback(stylusBatteryCallback)
clearInvocations(inputManager)
}
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
@Test
fun startListener_hasNotStarted_registersInputDeviceListener() {
stylusManager =
@@ -206,8 +226,7 @@
@Test
fun onInputDeviceAdded_btStylus_firstUsed_setsFlag() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
-
- verify(inputManager, times(1)).setStylusEverUsed(mContext, true)
+ verify({ InputSettings.setStylusEverUsed(mContext, true) }, times(1))
}
@Test
@@ -481,7 +500,7 @@
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
- verify(inputManager).setStylusEverUsed(mContext, true)
+ verify({ InputSettings.setStylusEverUsed(mContext, true) }, times(1))
}
@Test
@@ -494,13 +513,55 @@
}
@Test
- fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() {
- whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(true)
+ fun onBatteryStateChanged_batteryPresent_notInUsiSession_logsSessionStart() {
whenever(batteryState.isPresent).thenReturn(true)
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
- verify(inputManager, never()).setStylusEverUsed(mContext, true)
+ verify(uiEventLogger, times(1))
+ .log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED)
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryPresent_inUsiSession_doesNotLogSessionStart() {
+ whenever(batteryState.isPresent).thenReturn(true)
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+ clearInvocations(uiEventLogger)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(uiEventLogger, never()).log(any())
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryAbsent_notInUsiSession_doesNotLogSessionEnd() {
+ whenever(batteryState.isPresent).thenReturn(false)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(uiEventLogger, never()).log(any())
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryAbsent_inUsiSession_logSessionEnd() {
+ whenever(batteryState.isPresent).thenReturn(true)
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+ whenever(batteryState.isPresent).thenReturn(false)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(uiEventLogger, times(1)).log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED)
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() {
+ whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(true)
+
+ whenever(batteryState.isPresent).thenReturn(true)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify({ InputSettings.setStylusEverUsed(mContext, true) }, never())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
index cc6be5e..82b80f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -92,6 +92,14 @@
}
@Test
+ fun start_registersCallbacks() {
+ startable.start()
+
+ verify(stylusManager, times(1)).registerCallback(startable)
+ verify(stylusManager, times(1)).registerBatteryCallback(startable)
+ }
+
+ @Test
fun onStylusAdded_internal_updatesNotificationSuppression() {
startable.onStylusAdded(STYLUS_DEVICE_ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index fc7436a..586bdc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -27,6 +27,7 @@
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.animation.doOnCancel
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
@@ -361,6 +362,105 @@
}
@Test
+ fun displayView_loading_animationStarted() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ assertThat(underTest.loadingDetails!!.animator.isStarted).isTrue()
+ }
+
+ @Test
+ fun displayView_notLoading_noAnimation() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ assertThat(underTest.loadingDetails).isNull()
+ }
+
+ @Test
+ fun displayView_loadingThenNotLoading_animationStopped() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val animator = underTest.loadingDetails!!.animator
+ var cancelled = false
+ animator.doOnCancel { cancelled = true }
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Button(Text.Loaded("button")) {},
+ )
+ )
+
+ assertThat(cancelled).isTrue()
+ assertThat(underTest.loadingDetails).isNull()
+ }
+
+ @Test
+ fun displayView_loadingThenHideView_animationStopped() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val animator = underTest.loadingDetails!!.animator
+ var cancelled = false
+ animator.doOnCancel { cancelled = true }
+
+ underTest.removeView(DEVICE_ID, "TestReason")
+
+ assertThat(cancelled).isTrue()
+ assertThat(underTest.loadingDetails).isNull()
+ }
+
+ @Test
+ fun displayView_loadingThenNewLoading_animationStaysTheSame() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val animator = underTest.loadingDetails!!.animator
+ var cancelled = false
+ animator.doOnCancel { cancelled = true }
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("new text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ assertThat(underTest.loadingDetails!!.animator).isEqualTo(animator)
+ assertThat(underTest.loadingDetails!!.animator.isStarted).isTrue()
+ assertThat(cancelled).isFalse()
+ }
+
+ @Test
fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
createChipbarInfo(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index cc23485..0c119fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -384,14 +384,14 @@
UserInfo(
/* id= */ 818,
/* name= */ "non_guest",
- /* flags= */ 0,
+ /* flags= */ UserInfo.FLAG_FULL,
)
private val GUEST_USER_INFO =
UserInfo(
/* id= */ 669,
/* name= */ "guest",
/* iconPath= */ "",
- /* flags= */ 0,
+ /* flags= */ UserInfo.FLAG_FULL,
UserManager.USER_TYPE_FULL_GUEST,
)
private val EPHEMERAL_GUEST_USER_INFO =
@@ -399,7 +399,7 @@
/* id= */ 669,
/* name= */ "guest",
/* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_EPHEMERAL,
+ /* flags= */ UserInfo.FLAG_EPHEMERAL or UserInfo.FLAG_FULL,
UserManager.USER_TYPE_FULL_GUEST,
)
private val ALL_USERS =
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 3538d9b..22d05bf 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
@@ -28,7 +28,6 @@
import android.os.UserManager
import android.provider.Settings
import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
@@ -89,6 +88,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@@ -147,6 +147,7 @@
featureFlags = featureFlags,
),
manager = manager,
+ headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
TelephonyInteractor(
@@ -890,6 +891,50 @@
assertThat(selectedUser()).isNotNull()
}
+ @Test
+ fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(manager.getUserSwitchability(any()))
+ .thenReturn(UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED)
+ val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true
+ )
+ )
+
+ runCurrent()
+ underTest.userRecords.value
+ .filter { it.info == null }
+ .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
+ }
+
+ @Test
+ fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true)
+ whenever(manager.isUserUnlocked(anyInt())).thenReturn(false)
+ val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true
+ )
+ )
+
+ runCurrent()
+ underTest.userRecords.value
+ .filter { it.info == null }
+ .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
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 8f0375f..2f05e65 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
@@ -41,6 +41,7 @@
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
@@ -71,6 +72,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@@ -252,6 +254,7 @@
),
featureFlags = featureFlags,
manager = manager,
+ headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
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 71a112c..9268904 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
@@ -41,6 +41,7 @@
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -72,6 +73,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@@ -154,6 +156,7 @@
),
featureFlags = featureFlags,
manager = manager,
+ headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
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
index 5ef62c1..b367a60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
@@ -60,6 +60,11 @@
mCallback = callback;
}
+ public FakeConditionalCoreStartable(Monitor monitor, Callback callback) {
+ super(monitor);
+ mCallback = callback;
+ }
+
@Override
protected void onStart() {
mCallback.onStart();
@@ -122,6 +127,31 @@
verify(mMonitor).removeSubscription(mSubscriptionToken);
}
+ @Test
+ public void testOnStartCallbackWithNoConditions() {
+ final CoreStartable coreStartable =
+ new FakeConditionalCoreStartable(mMonitor,
+ 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()).isEmpty();
+
+ verify(mCallback, never()).onStart();
+
+ subscription.getCallback().onConditionsChanged(true);
+
+ verify(mCallback).onStart();
+ verify(mMonitor).removeSubscription(mSubscriptionToken);
+ }
+
/**
* Verifies that {@link ConditionalCoreStartable#bootCompleted()} ()} is predicated on
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 185d408..e185922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -82,8 +82,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
@@ -285,7 +283,7 @@
@Mock
private ShadeWindowLogger mShadeWindowLogger;
@Mock
- private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
+ private NotifPipelineFlags mNotifPipelineFlags;
private TestableBubblePositioner mPositioner;
@@ -298,8 +296,6 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- setTestFlagResolver(mFlagResolver);
-
mTestableLooper = TestableLooper.get(this);
// For the purposes of this test, just run everything synchronously
@@ -318,24 +314,6 @@
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
- // Need notifications for bubbles
- mNotificationTestHelper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
- mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
- mNonBubbleNotifRow = mNotificationTestHelper.createRow();
- mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
- mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
-
- UserHandle handle = mock(UserHandle.class);
- when(handle.getIdentifier()).thenReturn(11);
- mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry(
- mNotificationTestHelper.createBubble(handle));
- mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
- mNotificationTestHelper.createBubble(handle));
-
mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
mAppBubbleIntent.setPackage(mContext.getPackageName());
@@ -420,9 +398,28 @@
mNotifPipeline,
mSysUiState,
mock(FeatureFlags.class),
+ mNotifPipelineFlags,
syncExecutor);
mBubblesManager.addNotifCallback(mNotifCallback);
+ // Need notifications for bubbles
+ mNotificationTestHelper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
+ mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
+ mNonBubbleNotifRow = mNotificationTestHelper.createRow();
+ mBubbleEntry = mBubblesManager.notifToBubbleEntry(mRow);
+ mBubbleEntry2 = mBubblesManager.notifToBubbleEntry(mRow2);
+
+ UserHandle handle = mock(UserHandle.class);
+ when(handle.getIdentifier()).thenReturn(11);
+ mBubbleEntryUser11 = mBubblesManager.notifToBubbleEntry(
+ mNotificationTestHelper.createBubble(handle));
+ mBubbleEntry2User11 = mBubblesManager.notifToBubbleEntry(
+ mNotificationTestHelper.createBubble(handle));
+
// Get a reference to the BubbleController's entry listener
verify(mNotifPipeline, atLeastOnce())
.addCollectionListener(mNotifListenerCaptor.capture());
@@ -1185,7 +1182,7 @@
@Test
public void testDeleteShortcutsDeletesXml() throws Exception {
ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry());
+ BubbleEntry shortcutBubbleEntry = mBubblesManager.notifToBubbleEntry(row.getEntry());
mBubbleController.updateBubble(shortcutBubbleEntry);
mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(),
@@ -1297,7 +1294,7 @@
entry.getChannel().setConversationId(
row.getEntry().getChannel().getParentChannelId(),
"shortcutId");
- mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
+ mBubbleController.updateBubble(mBubblesManager.notifToBubbleEntry(row.getEntry()));
assertTrue(mBubbleController.hasBubbles());
// Overflow it
@@ -1323,7 +1320,7 @@
entry.getChannel().setConversationId(
row.getEntry().getChannel().getParentChannelId(),
"shortcutId");
- mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
+ mBubbleController.updateBubble(mBubblesManager.notifToBubbleEntry(row.getEntry()));
assertTrue(mBubbleController.hasBubbles());
// Overflow it
@@ -1706,13 +1703,13 @@
@Test
public void testCreateBubbleFromOngoingNotification_OngoingDismissalEnabled() {
- when(mFlagResolver.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)).thenReturn(true);
+ when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
NotificationEntry notif = new NotificationEntryBuilder()
.setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
.setCanBubble(true)
.build();
- BubbleEntry bubble = BubblesManager.notifToBubbleEntry(notif);
+ BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
assertTrue("Ongoing Notifis should be dismissable", bubble.isDismissable());
}
@@ -1720,13 +1717,13 @@
@Test
public void testCreateBubbleFromNoDismissNotification_OngoingDismissalEnabled() {
- when(mFlagResolver.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)).thenReturn(true);
+ when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
NotificationEntry notif = new NotificationEntryBuilder()
.setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
.setCanBubble(true)
.build();
- BubbleEntry bubble = BubblesManager.notifToBubbleEntry(notif);
+ BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
assertFalse("FLAG_NO_DISMISS Notifs should be non-dismissable", bubble.isDismissable());
}
@@ -1738,7 +1735,7 @@
.setCanBubble(true)
.build();
- BubbleEntry bubble = BubblesManager.notifToBubbleEntry(notif);
+ BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
assertFalse(
"Ongoing Notifis should be dismissable, if the feature is off",
@@ -1754,7 +1751,7 @@
.setCanBubble(true)
.build();
- BubbleEntry bubble = BubblesManager.notifToBubbleEntry(notif);
+ BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
assertTrue(
"FLAG_NO_DISMISS should be ignored, if the feature is off",
@@ -1772,7 +1769,7 @@
workEntry.setBubbleMetadata(getMetadata());
workEntry.setFlagBubble(true);
- return new Bubble(BubblesManager.notifToBubbleEntry(workEntry),
+ return new Bubble(mBubblesManager.notifToBubbleEntry(workEntry),
null,
mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor());
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index c236bc9..1bab997 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -34,7 +34,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -145,7 +144,6 @@
disallowTestableLooperAsMainThread();
mContext.cleanUpReceivers(this.getClass().getSimpleName());
mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName());
- setTestFlagResolver(null);
}
@AfterClass
@@ -208,10 +206,6 @@
}
}
- protected void setTestFlagResolver(SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = flagResolver;
- }
-
public static void waitForIdleSync(Handler h) {
validateThread(h.getLooper());
Idler idler = new Idler(null);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java
new file mode 100644
index 0000000..7ee05d0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.condition;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+/**
+ * {@link SelfExecutingMonitor} creates a monitor that independently executes its logic through
+ * a {@link FakeExecutor}, which is ran at when a subscription is added and removed.
+ */
+public class SelfExecutingMonitor extends Monitor {
+ private final FakeExecutor mExecutor;
+
+ /**
+ * Default constructor that allows specifying the FakeExecutor to use.
+ */
+ public SelfExecutingMonitor(FakeExecutor executor) {
+ super(executor);
+ mExecutor = executor;
+ }
+
+ @Override
+ public Subscription.Token addSubscription(@NonNull Subscription subscription) {
+ final Subscription.Token result = super.addSubscription(subscription);
+ mExecutor.runAllReady();
+ return result;
+ }
+
+ @Override
+ public void removeSubscription(@NonNull Subscription.Token token) {
+ super.removeSubscription(token);
+ mExecutor.runNextReady();
+ }
+
+ /**
+ * Creates a {@link SelfExecutingMonitor} with a self-managed {@link FakeExecutor}. Use only
+ * for cases where condition state only will be set at when a subscription is added.
+ */
+ public static SelfExecutingMonitor createInstance() {
+ final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ final FakeExecutor mExecutor = new FakeExecutor(mFakeSystemClock);
+ return new SelfExecutingMonitor(mExecutor);
+ }
+}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 12a8230..4702734 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -396,5 +396,11 @@
// Note: this is a base ID, multiple notifications will be posted for each
// abusive apps, with notification ID based off this ID.
NOTE_ABUSIVE_BG_APPS_BASE = 0xc1b2508; // 203105544
+
+ // Notify the user that dialer and sms functionality are unavailable whilst the apps are
+ // paused in the work profile.
+ // Package: android
+ NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006;
+
}
}
diff --git a/services/api/current.txt b/services/api/current.txt
index a4deed3..5c7b947 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -85,69 +85,72 @@
method @Nullable public String getAppComponentFactory();
method @Nullable public String getApplicationClassName();
method @Nullable public String getBackupAgentName();
- method @DrawableRes public int getBannerRes();
+ method @DrawableRes public int getBannerResourceId();
method public int getBaseRevisionCode();
method public int getCategory();
method @Nullable public String getClassLoaderName();
method @Dimension(unit=android.annotation.Dimension.DP) public int getCompatibleWidthLimitDp();
- method @XmlRes public int getDataExtractionRulesRes();
- method @StringRes public int getDescriptionRes();
- method @XmlRes public int getFullBackupContentRes();
+ method @XmlRes public int getDataExtractionRulesResourceId();
+ method @StringRes public int getDescriptionResourceId();
+ method @XmlRes public int getFullBackupContentResourceId();
method public int getGwpAsanMode();
- method @DrawableRes public int getIconRes();
- method @StringRes public int getLabelRes();
+ method @DrawableRes public int getIconResourceId();
+ method @StringRes public int getLabelResourceId();
method @Dimension(unit=android.annotation.Dimension.DP) public int getLargestWidthLimitDp();
method @NonNull public java.util.List<java.lang.String> getLibraryNames();
- method @XmlRes public int getLocaleConfigRes();
- method @DrawableRes public int getLogoRes();
+ method @XmlRes public int getLocaleConfigResourceId();
+ method @DrawableRes public int getLogoResourceId();
method public long getLongVersionCode();
method public float getMaxAspectRatio();
method public float getMinAspectRatio();
method public int getNativeHeapZeroInitialized();
- method @XmlRes public int getNetworkSecurityConfigRes();
+ method @XmlRes public int getNetworkSecurityConfigResourceId();
method @Nullable public String getRequiredAccountType();
method @Dimension(unit=android.annotation.Dimension.DP) public int getRequiresSmallestWidthDp();
method @Nullable public String getRestrictedAccountType();
- method @DrawableRes public int getRoundIconRes();
+ method @DrawableRes public int getRoundIconResourceId();
method @Nullable public String getSdkLibraryName();
method @Nullable public String getSharedUserId();
- method @StringRes public int getSharedUserLabelRes();
+ method @StringRes public int getSharedUserLabelResourceId();
method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
method @Nullable public String getStaticSharedLibraryName();
method @NonNull public java.util.UUID getStorageUuid();
method public int getTargetSdkVersion();
- method @StyleRes public int getThemeRes();
+ method @StyleRes public int getThemeResourceId();
method public int getUiOptions();
method @Nullable public String getVersionName();
method @Nullable public String getZygotePreloadName();
+ method public boolean is32BitAbiPreferred();
method public boolean isAllowAudioPlaybackCapture();
- method public boolean isAllowBackup();
- method public boolean isAllowClearUserData();
- method public boolean isAllowClearUserDataOnFailedRestore();
method public boolean isAllowNativeHeapPointerTagging();
- method public boolean isAllowTaskReparenting();
method public boolean isAnyDensity();
method public boolean isAttributionsUserVisible();
+ method public boolean isBackupAllowed();
method public boolean isBackupInForeground();
- method public boolean isCantSaveState();
+ method public boolean isClearUserDataAllowed();
+ method public boolean isClearUserDataOnFailedRestoreAllowed();
+ method public boolean isCleartextTrafficAllowed();
method public boolean isCoreApp();
method public boolean isCrossProfile();
method public boolean isDebuggable();
+ method public boolean isDeclaredHavingCode();
method public boolean isDefaultToDeviceProtectedStorage();
method public boolean isDirectBootAware();
- method public boolean isExtractNativeLibs();
+ method public boolean isExtraLargeScreensSupported();
+ method public boolean isExtractNativeLibrariesRequested();
method public boolean isFactoryTest();
method public boolean isForceQueryable();
method public boolean isFullBackupOnly();
method public boolean isHardwareAccelerated();
- method public boolean isHasCode();
- method public boolean isHasFragileUserData();
method public boolean isIsolatedSplitLoading();
- method public boolean isKillAfterRestore();
+ method public boolean isKillAfterRestoreAllowed();
method public boolean isLargeHeap();
+ method public boolean isLargeScreensSupported();
method public boolean isLeavingSharedUser();
method public boolean isMultiArch();
method public boolean isNativeLibraryRootRequiresIsa();
+ method public boolean isNonSdkApiRequested();
+ method public boolean isNormalScreensSupported();
method public boolean isOnBackInvokedCallbackEnabled();
method public boolean isPersistent();
method public boolean isProfileable();
@@ -157,17 +160,14 @@
method public boolean isResetEnabledSettingsOnAppDataCleared();
method public boolean isResourceOverlay();
method public boolean isRestoreAnyVersion();
+ method public boolean isRtlSupported();
+ method public boolean isSaveStateDisallowed();
method public boolean isSignedWithPlatformKey();
- method public boolean isSupportsExtraLargeScreens();
- method public boolean isSupportsLargeScreens();
- method public boolean isSupportsNormalScreens();
- method public boolean isSupportsRtl();
- method public boolean isSupportsSmallScreens();
+ method public boolean isSmallScreensSupported();
+ method public boolean isTaskReparentingAllowed();
method public boolean isTestOnly();
- method public boolean isUse32BitAbi();
method public boolean isUseEmbeddedDex();
- method public boolean isUsesCleartextTraffic();
- method public boolean isUsesNonSdkApi();
+ method public boolean isUserDataFragile();
method public boolean isVmSafeMode();
}
@@ -299,11 +299,11 @@
public static final class ActivityInterceptorCallback.ActivityInterceptorInfo.Builder {
ctor public ActivityInterceptorCallback.ActivityInterceptorInfo.Builder(int, int, int, int, int, @NonNull android.content.Intent, @NonNull android.content.pm.ResolveInfo, @NonNull android.content.pm.ActivityInfo);
method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo build();
- method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingFeatureId(@NonNull String);
- method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingPackage(@NonNull String);
- method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCheckedOptions(@NonNull android.app.ActivityOptions);
- method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setClearOptionsAnimationRunnable(@NonNull Runnable);
- method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setResolvedType(@NonNull String);
+ method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingFeatureId(@Nullable String);
+ method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingPackage(@Nullable String);
+ method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCheckedOptions(@Nullable android.app.ActivityOptions);
+ method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setClearOptionsAnimationRunnable(@Nullable Runnable);
+ method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setResolvedType(@Nullable String);
}
public class ActivityInterceptorCallbackRegistry {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 24d7c90..598521f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1458,7 +1458,7 @@
if (((response.getDatasets() == null || response.getDatasets().isEmpty())
&& response.getAuthentication() == null)
|| autofillDisabled) {
- // Response is "empty" from an UI point of view, need to notify client.
+ // Response is "empty" from a UI point of view, need to notify client.
notifyUnavailableToClient(
autofillDisabled ? AutofillManager.STATE_DISABLED_BY_SERVICE : 0,
/* autofillableIds= */ null);
@@ -4283,7 +4283,7 @@
mService.resetLastResponse();
- // The default autofill service cannot fullfill the request, let's check if the augmented
+ // The default autofill service cannot fulfill the request, let's check if the augmented
// autofill service can.
mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags);
if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index ec7e993..7804ebf 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -17,7 +17,9 @@
package com.android.server.companion.virtual;
import android.annotation.NonNull;
-import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.annotation.Nullable;
+import android.companion.virtual.sensor.IVirtualSensorCallback;
+import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.os.IBinder;
@@ -40,17 +42,30 @@
private static final String TAG = "SensorController";
+ // See system/core/libutils/include/utils/Errors.h
+ private static final int OK = 0;
+ private static final int UNKNOWN_ERROR = (-2147483647 - 1); // INT32_MIN value
+ private static final int BAD_VALUE = -22;
+
private final Object mLock;
private final int mVirtualDeviceId;
@GuardedBy("mLock")
private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+ @NonNull
+ private final SensorManagerInternal.RuntimeSensorCallback mRuntimeSensorCallback;
private final SensorManagerInternal mSensorManagerInternal;
+ private final VirtualDeviceManagerInternal mVdmInternal;
- public SensorController(@NonNull Object lock, int virtualDeviceId) {
+
+
+ public SensorController(@NonNull Object lock, int virtualDeviceId,
+ @Nullable IVirtualSensorCallback virtualSensorCallback) {
mLock = lock;
mVirtualDeviceId = virtualDeviceId;
+ mRuntimeSensorCallback = new RuntimeSensorCallbackWrapper(virtualSensorCallback);
mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+ mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
}
void close() {
@@ -67,36 +82,23 @@
}
}
- void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
- Objects.requireNonNull(deviceToken);
+ int createSensor(@NonNull IBinder sensorToken, @NonNull VirtualSensorConfig config) {
+ Objects.requireNonNull(sensorToken);
Objects.requireNonNull(config);
try {
- createSensorInternal(deviceToken, config);
+ return createSensorInternal(sensorToken, config);
} catch (SensorCreationException e) {
throw new RuntimeException(
"Failed to create virtual sensor '" + config.getName() + "'.", e);
}
}
- private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+ private int createSensorInternal(IBinder sensorToken, VirtualSensorConfig config)
throws SensorCreationException {
- final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
- (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
- IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
- if (callback != null) {
- try {
- callback.onStateChanged(
- enabled, samplingPeriodMicros, batchReportLatencyMicros);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed to call sensor callback.", e);
- }
- }
- };
-
final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
config.getType(), config.getName(),
config.getVendor() == null ? "" : config.getVendor(),
- runtimeSensorCallback);
+ mRuntimeSensorCallback);
if (handle <= 0) {
throw new SensorCreationException("Received an invalid virtual sensor handle.");
}
@@ -104,8 +106,8 @@
// The handle is valid from here, so ensure that all failures clean it up.
final BinderDeathRecipient binderDeathRecipient;
try {
- binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ binderDeathRecipient = new BinderDeathRecipient(sensorToken);
+ sensorToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
} catch (RemoteException e) {
mSensorManagerInternal.removeRuntimeSensor(handle);
throw new SensorCreationException("Client died before sensor could be created.", e);
@@ -114,8 +116,9 @@
synchronized (mLock) {
SensorDescriptor sensorDescriptor = new SensorDescriptor(
handle, config.getType(), config.getName(), binderDeathRecipient);
- mSensorDescriptors.put(deviceToken, sensorDescriptor);
+ mSensorDescriptors.put(sensorToken, sensorDescriptor);
}
+ return handle;
}
boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
@@ -178,6 +181,39 @@
}
}
+ private final class RuntimeSensorCallbackWrapper
+ implements SensorManagerInternal.RuntimeSensorCallback {
+ @Nullable
+ private IVirtualSensorCallback mCallback;
+
+ RuntimeSensorCallbackWrapper(@Nullable IVirtualSensorCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public int onConfigurationChanged(int handle, boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros) {
+ if (mCallback == null) {
+ Slog.e(TAG, "No sensor callback configured for sensor handle " + handle);
+ return BAD_VALUE;
+ }
+ VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, handle);
+ if (sensor == null) {
+ Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId
+ + " and sensor handle=" + handle);
+ return BAD_VALUE;
+ }
+ try {
+ mCallback.onConfigurationChanged(sensor, enabled, samplingPeriodMicros,
+ batchReportLatencyMicros);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to call sensor callback: " + e);
+ return UNKNOWN_ERROR;
+ }
+ return OK;
+ }
+ }
+
@VisibleForTesting
static final class SensorDescriptor {
@@ -207,10 +243,10 @@
}
private final class BinderDeathRecipient implements IBinder.DeathRecipient {
- private final IBinder mDeviceToken;
+ private final IBinder mSensorToken;
- BinderDeathRecipient(IBinder deviceToken) {
- mDeviceToken = deviceToken;
+ BinderDeathRecipient(IBinder sensorToken) {
+ mSensorToken = sensorToken;
}
@Override
@@ -219,7 +255,7 @@
// quitting, which removes this death recipient. If this is invoked, the remote end
// died, or they disposed of the object without properly unregistering.
Slog.e(TAG, "Virtual sensor controller binder died");
- unregisterSensor(mDeviceToken);
+ unregisterSensor(mSensorToken);
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 7fce442..b4dcf43 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -41,6 +41,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
@@ -86,6 +87,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -131,6 +134,11 @@
@GuardedBy("mVirtualDeviceLock")
@Nullable
private LocaleList mLocaleList = null;
+ // This device's sensors, keyed by sensor handle.
+ @GuardedBy("mVirtualDeviceLock")
+ private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
+ @GuardedBy("mVirtualDeviceLock")
+ private List<VirtualSensor> mVirtualSensorList = null;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@@ -240,10 +248,15 @@
mInputController = inputController;
}
if (sensorController == null) {
- mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+ mSensorController = new SensorController(
+ mVirtualDeviceLock, mDeviceId, mParams.getVirtualSensorCallback());
} else {
mSensorController = sensorController;
}
+ final List<VirtualSensorConfig> virtualSensorConfigs = mParams.getVirtualSensorConfigs();
+ for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
+ createVirtualSensor(virtualSensorConfigs.get(i));
+ }
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
mOnDeviceCloseListener = onDeviceCloseListener;
@@ -384,6 +397,8 @@
mVirtualAudioController = null;
}
mLocaleList = null;
+ mVirtualSensorList = null;
+ mVirtualSensors.clear();
}
mOnDeviceCloseListener.onClose(mDeviceId);
mAppToken.unlinkToDeath(this, 0);
@@ -698,17 +713,17 @@
}
}
- @Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void createVirtualSensor(
- @NonNull IBinder deviceToken,
- @NonNull VirtualSensorConfig config) {
- super.createVirtualSensor_enforcePermission();
- Objects.requireNonNull(config);
- Objects.requireNonNull(deviceToken);
+ private void createVirtualSensor(@NonNull VirtualSensorConfig config) {
+ final IBinder sensorToken =
+ new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
final long ident = Binder.clearCallingIdentity();
try {
- mSensorController.createSensor(deviceToken, config);
+ int handle = mSensorController.createSensor(sensorToken, config);
+ VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
+ this, sensorToken);
+ synchronized (mVirtualDeviceLock) {
+ mVirtualSensors.put(handle, sensor);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -716,13 +731,24 @@
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void unregisterSensor(@NonNull IBinder token) {
- super.unregisterSensor_enforcePermission();
- final long ident = Binder.clearCallingIdentity();
- try {
- mSensorController.unregisterSensor(token);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ @Nullable
+ public List<VirtualSensor> getVirtualSensorList() {
+ super.getVirtualSensorList_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ if (mVirtualSensorList == null) {
+ mVirtualSensorList = new ArrayList<>();
+ for (int i = 0; i < mVirtualSensors.size(); ++i) {
+ mVirtualSensorList.add(mVirtualSensors.valueAt(i));
+ }
+ mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
+ }
+ return mVirtualSensorList;
+ }
+ }
+
+ VirtualSensor getVirtualSensorByHandle(int handle) {
+ synchronized (mVirtualDeviceLock) {
+ return mVirtualSensors.get(handle);
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index f39c32df..9bb05a6 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -34,6 +34,7 @@
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.sensor.VirtualSensor;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
@@ -470,6 +471,17 @@
}
@Override
+ public @Nullable VirtualSensor getVirtualSensor(int deviceId, int handle) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ if (virtualDevice != null) {
+ return virtualDevice.getVirtualSensorByHandle(handle);
+ }
+ }
+ return null;
+ }
+
+ @Override
public @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid) {
ArraySet<Integer> result = new ArraySet<>();
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 6fb5730..26d0860 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1329,6 +1329,11 @@
/** @deprecated For legacy shell command only. */
@Deprecated
+ public abstract void legacyForceDexOpt(@NonNull String packageName)
+ throws LegacyDexoptDisabledException;
+
+ /** @deprecated For legacy shell command only. */
+ @Deprecated
public abstract void legacyReconcileSecondaryDexFiles(String packageName)
throws LegacyDexoptDisabledException;
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 54c47b2..669dcfc 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -892,9 +892,9 @@
pw.println("Battery service (battery) commands:");
pw.println(" help");
pw.println(" Print this help text.");
- pw.println(" get [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid]");
- pw.println(
- " set [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid] <value>");
+ pw.println(" get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]");
+ pw.println(" set [-f] "
+ + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>");
pw.println(" Force a battery property value, freezing battery state.");
pw.println(" -f: force a battery change broadcast be sent, prints new sequence.");
pw.println(" unplug [-f]");
@@ -954,6 +954,9 @@
case "wireless":
pw.println(mHealthInfo.chargerWirelessOnline);
break;
+ case "dock":
+ pw.println(mHealthInfo.chargerDockOnline);
+ break;
case "status":
pw.println(mHealthInfo.batteryStatus);
break;
@@ -1008,6 +1011,9 @@
case "wireless":
mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
break;
+ case "dock":
+ mHealthInfo.chargerDockOnline = Integer.parseInt(value) != 0;
+ break;
case "status":
mHealthInfo.batteryStatus = Integer.parseInt(value);
break;
@@ -1085,6 +1091,7 @@
mHealthInfo.chargerAcOnline = false;
mHealthInfo.chargerUsbOnline = false;
mHealthInfo.chargerWirelessOnline = false;
+ mHealthInfo.chargerDockOnline = false;
mUpdatesStopped = true;
Binder.withCleanCallingIdentity(() -> processValuesLocked(forceUpdate, pw));
}
@@ -1127,6 +1134,7 @@
pw.println(" AC powered: " + mHealthInfo.chargerAcOnline);
pw.println(" USB powered: " + mHealthInfo.chargerUsbOnline);
pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
+ pw.println(" Dock powered: " + mHealthInfo.chargerDockOnline);
pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps);
pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts);
pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounterUah);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index f49dfeb..9d07365 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1199,7 +1199,7 @@
}
/**
- * JobService to measure all covered binaries and record result to Westworld.
+ * JobService to measure all covered binaries and record results to statsd.
*/
public static class UpdateMeasurementsJobService extends JobService {
private static long sTimeLastRanMs = 0;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 2aeaa2f..13f327c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -260,6 +260,10 @@
*/
private static final String KEY_LOW_SWAP_THRESHOLD_PERCENT = "low_swap_threshold_percent";
+ /** Default value for mFlagApplicationStartInfoEnabled. Defaults to false. */
+ private static final String KEY_DEFAULT_APPLICATION_START_INFO_ENABLED =
+ "enable_app_start_info";
+
/**
* Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
* Settings.Global. This allows it to be set experimentally unless it has been
@@ -550,6 +554,9 @@
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
+ // Indicates whether ApplicationStartInfo is enabled.
+ volatile boolean mFlagApplicationStartInfoEnabled;
+
// Indicates whether the background activity starts is enabled.
// Controlled by Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED.
// If not set explicitly the default is controlled by DeviceConfig.
@@ -996,6 +1003,9 @@
case KEY_MAX_CACHED_PROCESSES:
updateMaxCachedProcesses();
break;
+ case KEY_DEFAULT_APPLICATION_START_INFO_ENABLED:
+ updateApplicationStartInfoEnabled();
+ break;
case KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED:
updateBackgroundActivityStarts();
break;
@@ -1390,6 +1400,14 @@
Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, 1) == 1;
}
+ private void updateApplicationStartInfoEnabled() {
+ mFlagApplicationStartInfoEnabled =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFAULT_APPLICATION_START_INFO_ENABLED,
+ /*defaultValue*/ false);
+ }
+
private void updateBackgroundActivityStarts() {
mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1970,6 +1988,10 @@
pw.println(mFgToBgFgsGraceDuration);
pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("=");
pw.println(mFgsStartForegroundTimeoutMs);
+ pw.print(" ");
+ pw.print(KEY_DEFAULT_APPLICATION_START_INFO_ENABLED);
+ pw.print("=");
+ pw.println(mFlagApplicationStartInfoEnabled);
pw.print(" "); pw.print(KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED); pw.print("=");
pw.println(mFlagBackgroundActivityStartsEnabled);
pw.print(" "); pw.print(KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4f2f001..9085e2a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -184,6 +184,7 @@
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
+import android.app.ApplicationStartInfo;
import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
@@ -192,6 +193,7 @@
import android.app.ForegroundServiceDelegationOptions;
import android.app.IActivityController;
import android.app.IActivityManager;
+import android.app.IApplicationStartInfoCompleteListener;
import android.app.IApplicationThread;
import android.app.IForegroundServiceObserver;
import android.app.IInstrumentationWatcher;
@@ -485,6 +487,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import java.util.function.Supplier;
public class ActivityManagerService extends IActivityManager.Stub
@@ -6450,6 +6453,44 @@
return entry == null ? null : entry.second;
}
+ private static class GetBackgroundStartPrivilegesFunctor implements Consumer<ProcessRecord> {
+ private BackgroundStartPrivileges mBackgroundStartPrivileges =
+ BackgroundStartPrivileges.NONE;
+ private int mUid;
+
+ void prepare(int uid) {
+ mUid = uid;
+ mBackgroundStartPrivileges = BackgroundStartPrivileges.NONE;
+ }
+
+ @NonNull
+ BackgroundStartPrivileges getResult() {
+ return mBackgroundStartPrivileges;
+ }
+
+ public void accept(ProcessRecord pr) {
+ if (pr.uid == mUid) {
+ mBackgroundStartPrivileges =
+ mBackgroundStartPrivileges.merge(pr.getBackgroundStartPrivileges());
+ }
+ }
+ }
+
+ private final GetBackgroundStartPrivilegesFunctor mGetBackgroundStartPrivilegesFunctor =
+ new GetBackgroundStartPrivilegesFunctor();
+
+ /**
+ * Returns the current complete {@link BackgroundStartPrivileges} of the UID.
+ */
+ @NonNull
+ private BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) {
+ synchronized (mProcLock) {
+ mGetBackgroundStartPrivilegesFunctor.prepare(uid);
+ mProcessList.forEachLruProcessesLOSP(false, mGetBackgroundStartPrivilegesFunctor);
+ return mGetBackgroundStartPrivilegesFunctor.getResult();
+ }
+ }
+
/**
* @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
* the allowlist
@@ -9467,6 +9508,37 @@
}
@Override
+ public ParceledListSlice<ApplicationStartInfo> getHistoricalProcessStartReasons(
+ String packageName, int maxNum, int userId) {
+ if (!mConstants.mFlagApplicationStartInfoEnabled) {
+ return new ParceledListSlice<ApplicationStartInfo>(
+ new ArrayList<ApplicationStartInfo>());
+ }
+ enforceNotIsolatedCaller("getHistoricalProcessStartReasons");
+
+ final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>();
+
+ return new ParceledListSlice<ApplicationStartInfo>(results);
+ }
+
+ @Override
+ public void setApplicationStartInfoCompleteListener(
+ IApplicationStartInfoCompleteListener listener, int userId) {
+ if (!mConstants.mFlagApplicationStartInfoEnabled) {
+ return;
+ }
+ enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener");
+ }
+
+ @Override
+ public void removeApplicationStartInfoCompleteListener(int userId) {
+ if (!mConstants.mFlagApplicationStartInfoEnabled) {
+ return;
+ }
+ enforceNotIsolatedCaller("removeApplicationStartInfoCompleteListener");
+ }
+
+ @Override
public ParceledListSlice<ApplicationExitInfo> getHistoricalProcessExitReasons(
String packageName, int pid, int maxNum, int userId) {
enforceNotIsolatedCaller("getHistoricalProcessExitReasons");
@@ -13707,6 +13779,17 @@
// not be used generally, so we will be marking them as exported by default
boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+
+ // A receiver that is visible to instant apps must also be exported.
+ final boolean unexportedReceiverVisibleToInstantApps =
+ ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
+ if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
+ throw new IllegalArgumentException(
+ "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
+ + "RECEIVER_NOT_EXPORTED flag");
+ }
+
// STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
// updating their receivers to be exempt from this requirement until their receivers
// are flagged.
@@ -16855,13 +16938,14 @@
@Override
public boolean startProfile(@UserIdInt int userId) {
- return mUserController.startProfile(userId);
+ return mUserController.startProfile(userId, /* evenWhenDisabled= */ false,
+ /* unlockListener= */ null);
}
@Override
public boolean startProfileWithListener(@UserIdInt int userId,
@Nullable IProgressListener unlockListener) {
- return mUserController.startProfile(userId, unlockListener);
+ return mUserController.startProfile(userId, /* evenWhenDisabled= */ false, unlockListener);
}
@Override
@@ -17821,6 +17905,11 @@
return mConstants.mFlagBackgroundActivityStartsEnabled;
}
+ @Override
+ public BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) {
+ return ActivityManagerService.this.getBackgroundStartPrivileges(uid);
+ }
+
public void reportCurKeyguardUsageEvent(boolean keyguardShowing) {
ActivityManagerService.this.reportGlobalUsageEvent(keyguardShowing
? UsageEvents.Event.KEYGUARD_SHOWN
@@ -18466,6 +18555,12 @@
// has a SHORT_FGS.
return mOomAdjuster.hasUidShortForegroundService(uid);
}
+
+ @Override
+ public boolean startProfileEvenWhenDisabled(@UserIdInt int userId) {
+ return mUserController.startProfile(userId, /* evenWhenDisabled= */ true,
+ /* unlockListener= */ null);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d29c327..05cf5dbb 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -229,14 +229,19 @@
* When defined, this receiver is considered "blocked" until at least the
* given count of other receivers have reached a terminal state; typically
* used for ordered broadcasts and priority traunches.
+ *
+ * @return the existing broadcast record in the queue that was replaced with a newer broadcast
+ * sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} or {@code null} if there
+ * wasn't any broadcast that was replaced.
*/
- public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
- @NonNull BroadcastConsumer replacedBroadcastConsumer, boolean wouldBeSkipped) {
+ @Nullable
+ public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
+ int recordIndex, boolean wouldBeSkipped) {
if (record.isReplacePending()) {
- final boolean didReplace = replaceBroadcast(record, recordIndex,
- replacedBroadcastConsumer, wouldBeSkipped);
- if (didReplace) {
- return;
+ final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex,
+ wouldBeSkipped);
+ if (replacedBroadcastRecord != null) {
+ return replacedBroadcastRecord;
}
}
@@ -253,34 +258,37 @@
// with implicit responsiveness expectations.
getQueueForBroadcast(record).addLast(newBroadcastArgs);
onBroadcastEnqueued(record, recordIndex, wouldBeSkipped);
+ return null;
}
/**
* Searches from newest to oldest in the pending broadcast queues, and at the first matching
* pending broadcast it finds, replaces it in-place and returns -- does not attempt to handle
* "duplicate" broadcasts in the queue.
- * <p>
- * @return {@code true} if it found and replaced an existing record in the queue;
- * {@code false} otherwise.
+ *
+ * @return the existing broadcast record in the queue that was replaced with a newer broadcast
+ * sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} or {@code null} if there
+ * wasn't any broadcast that was replaced.
*/
- private boolean replaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
- @NonNull BroadcastConsumer replacedBroadcastConsumer, boolean wouldBeSkipped) {
+ @Nullable
+ private BroadcastRecord replaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
+ boolean wouldBeSkipped) {
final ArrayDeque<SomeArgs> queue = getQueueForBroadcast(record);
- return replaceBroadcastInQueue(queue, record, recordIndex,
- replacedBroadcastConsumer, wouldBeSkipped);
+ return replaceBroadcastInQueue(queue, record, recordIndex, wouldBeSkipped);
}
/**
* Searches from newest to oldest, and at the first matching pending broadcast
* it finds, replaces it in-place and returns -- does not attempt to handle
* "duplicate" broadcasts in the queue.
- * <p>
- * @return {@code true} if it found and replaced an existing record in the queue;
- * {@code false} otherwise.
+ *
+ * @return the existing broadcast record in the queue that was replaced with a newer broadcast
+ * sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} or {@code null} if there
+ * wasn't any broadcast that was replaced.
*/
- private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
+ @Nullable
+ private BroadcastRecord replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastRecord record, int recordIndex,
- @NonNull BroadcastConsumer replacedBroadcastConsumer,
boolean wouldBeSkipped) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
@@ -302,11 +310,10 @@
record.copyEnqueueTimeFrom(testRecord);
onBroadcastDequeued(testRecord, testRecordIndex, testWouldBeSkipped);
onBroadcastEnqueued(record, recordIndex, wouldBeSkipped);
- replacedBroadcastConsumer.accept(testRecord, testRecordIndex);
- return true;
+ return testRecord;
}
}
- return false;
+ return null;
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index f954420..d4d6eb2 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -92,6 +92,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -204,6 +205,14 @@
@GuardedBy("mService")
private final ArrayList<Pair<BooleanSupplier, CountDownLatch>> mWaitingFor = new ArrayList<>();
+ /**
+ * Container for holding the set of broadcasts that have been replaced by a newer broadcast
+ * sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING}.
+ */
+ @GuardedBy("mService")
+ private final AtomicReference<ArraySet<BroadcastRecord>> mReplacedBroadcastsCache =
+ new AtomicReference<>();
+
private final BroadcastConstants mConstants;
private final BroadcastConstants mFgConstants;
private final BroadcastConstants mBgConstants;
@@ -627,9 +636,10 @@
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
- final ArraySet<BroadcastRecord> replacedBroadcasts = new ArraySet<>();
- final BroadcastConsumer replacedBroadcastConsumer =
- (record, i) -> replacedBroadcasts.add(record);
+ ArraySet<BroadcastRecord> replacedBroadcasts = mReplacedBroadcastsCache.getAndSet(null);
+ if (replacedBroadcasts == null) {
+ replacedBroadcasts = new ArraySet<>();
+ }
boolean enqueuedBroadcast = false;
for (int i = 0; i < r.receivers.size(); i++) {
@@ -653,7 +663,11 @@
}
}
enqueuedBroadcast = true;
- queue.enqueueOrReplaceBroadcast(r, i, replacedBroadcastConsumer, wouldBeSkipped);
+ final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast(
+ r, i, wouldBeSkipped);
+ if (replacedBroadcast != null) {
+ replacedBroadcasts.add(replacedBroadcast);
+ }
if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
"deferred at enqueue time");
@@ -664,7 +678,11 @@
// Skip any broadcasts that have been replaced by newer broadcasts with
// FLAG_RECEIVER_REPLACE_PENDING.
+ // TODO: Optimize and reuse mBroadcastConsumerSkipAndCanceled for the case of
+ // cancelling all receivers for a broadcast.
skipAndCancelReplacedBroadcasts(replacedBroadcasts);
+ replacedBroadcasts.clear();
+ mReplacedBroadcastsCache.compareAndSet(null, replacedBroadcasts);
// If nothing to dispatch, send any pending result immediately
if (r.receivers.isEmpty() || !enqueuedBroadcast) {
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8b34fe0..14ecf9f 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -376,7 +376,7 @@
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER,
ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER,
- true);
+ false); // assume false if the property is unknown
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a707202..fa3f684 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -20,6 +20,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.MY_PID;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
@@ -341,6 +342,24 @@
private boolean mInFullBackup;
/**
+ * A set of tokens that currently contribute to this process being temporarily allowed
+ * to start certain components (eg. activities or foreground services) even if it's not
+ * in the foreground.
+ */
+ @GuardedBy("mBackgroundStartPrivileges")
+ private final ArrayMap<Binder, BackgroundStartPrivileges> mBackgroundStartPrivileges =
+ new ArrayMap<>();
+
+ /**
+ * The merged BackgroundStartPrivileges based on what's in {@link #mBackgroundStartPrivileges}.
+ * This is lazily generated using {@link #getBackgroundStartPrivileges()}.
+ */
+ @Nullable
+ @GuardedBy("mBackgroundStartPrivileges")
+ private BackgroundStartPrivileges mBackgroundStartPrivilegesMerged =
+ BackgroundStartPrivileges.NONE;
+
+ /**
* Controller for driving the process state on the window manager side.
*/
private final WindowProcessController mWindowProcessController;
@@ -1325,11 +1344,50 @@
Objects.requireNonNull(entity);
mWindowProcessController.addOrUpdateBackgroundStartPrivileges(entity,
backgroundStartPrivileges);
+ setBackgroundStartPrivileges(entity, backgroundStartPrivileges);
}
void removeBackgroundStartPrivileges(Binder entity) {
Objects.requireNonNull(entity);
mWindowProcessController.removeBackgroundStartPrivileges(entity);
+ setBackgroundStartPrivileges(entity, null);
+ }
+
+ @NonNull
+ BackgroundStartPrivileges getBackgroundStartPrivileges() {
+ synchronized (mBackgroundStartPrivileges) {
+ if (mBackgroundStartPrivilegesMerged == null) {
+ // Lazily generate the merged version when it's actually needed.
+ mBackgroundStartPrivilegesMerged = BackgroundStartPrivileges.NONE;
+ for (int i = mBackgroundStartPrivileges.size() - 1; i >= 0; --i) {
+ mBackgroundStartPrivilegesMerged =
+ mBackgroundStartPrivilegesMerged.merge(
+ mBackgroundStartPrivileges.valueAt(i));
+ }
+ }
+ return mBackgroundStartPrivilegesMerged;
+ }
+ }
+
+ private void setBackgroundStartPrivileges(@NonNull Binder entity,
+ @Nullable BackgroundStartPrivileges backgroundStartPrivileges) {
+ synchronized (mBackgroundStartPrivileges) {
+ final boolean changed;
+ if (backgroundStartPrivileges == null) {
+ changed = mBackgroundStartPrivileges.remove(entity) != null;
+ } else {
+ final BackgroundStartPrivileges oldBsp =
+ mBackgroundStartPrivileges.put(entity, backgroundStartPrivileges);
+ // BackgroundStartPrivileges tries to reuse the same object and avoid creating
+ // additional objects. For now, we just compare the reference to see if something
+ // has changed.
+ // TODO: actually compare the individual values to see if there's a change
+ changed = backgroundStartPrivileges != oldBsp;
+ }
+ if (changed) {
+ mBackgroundStartPrivilegesMerged = null;
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b0a14ab..652f84d 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -55,6 +55,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -1463,18 +1464,24 @@
}
/**
- * Starts a user only if it's a profile, with a more relaxed permission requirement:
- * {@link android.Manifest.permission#MANAGE_USERS} or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
- * To be called from ActivityManagerService.
- * @param userId the id of the user to start.
- * @return true if the operation was successful.
+ * Starts a {@link UserManager#isProfile() profile user}.
+ *
+ * <p>To be called from {@link com.android.server.am.ActivityManagerService}.
+ *
+ * @param userId the id of the profile user to start.
+ * @param evenWhenDisabled whether the profile should be started if it's not enabled yet
+ * (most callers should pass {@code false}, except when starting the profile while it's
+ * being provisioned).
+ * @param unlockListener listener to be informed when the profile has started and unlocked.
+ *
+ * @return {@code true} if the operation was successful.
+ *
+ * @throws IllegalArgumentException if the user doesn't exist or is not a profile.
*/
- boolean startProfile(@UserIdInt int userId) {
- return startProfile(userId, /* unlockListener= */ null);
- }
-
- boolean startProfile(@UserIdInt int userId, @Nullable IProgressListener unlockListener) {
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ boolean startProfile(@UserIdInt int userId, boolean evenWhenDisabled,
+ @Nullable IProgressListener unlockListener) {
if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
== PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
@@ -1489,8 +1496,8 @@
throw new IllegalArgumentException("User " + userId + " is not a profile");
}
- if (!userInfo.isEnabled()) {
- Slogf.w(TAG, "Cannot start disabled profile #" + userId);
+ if (!userInfo.isEnabled() && !evenWhenDisabled) {
+ Slogf.w(TAG, "Cannot start disabled profile #%d", userId);
return false;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b001f3d..a88055d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1556,6 +1556,7 @@
case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE:
if (msg.arg1 != BluetoothProfile.HEADSET) {
synchronized (mDeviceStateLock) {
+ mBtHelper.onBtProfileDisconnected(msg.arg1);
mDeviceInventory.onBtProfileDisconnected(msg.arg1);
}
} else {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e55bddb..e48c538 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2402,10 +2402,6 @@
Log.w(TAG, "audioFormat to enable is not a surround format.");
return false;
}
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Missing WRITE_SETTINGS permission");
- }
final long token = Binder.clearCallingIdentity();
try {
@@ -2473,11 +2469,6 @@
/** @see AudioManager#getEncodedSurroundMode() */
@Override
public int getEncodedSurroundMode(int targetSdkVersion) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Missing WRITE_SETTINGS permission");
- }
-
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSettingsLock) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index df65dbd..4ab23c5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -278,7 +278,11 @@
}
AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
- mA2dp.setAvrcpAbsoluteVolume(index);
+ try {
+ mA2dp.setAvrcpAbsoluteVolume(index);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while changing abs volume", e);
+ }
}
/*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec(
@@ -286,7 +290,12 @@
if (mA2dp == null) {
return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
- final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ final BluetoothCodecStatus btCodecStatus = null;
+ try {
+ mA2dp.getCodecStatus(device);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while getting status of " + device, e);
+ }
if (btCodecStatus == null) {
return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
@@ -420,7 +429,11 @@
}
AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
- mLeAudio.setVolume(volume);
+ try {
+ mLeAudio.setVolume(volume);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while setting LE volume", e);
+ }
}
/*package*/ synchronized void setHearingAidVolume(int index, int streamType,
@@ -446,7 +459,11 @@
AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
}
- mHearingAid.setVolume(gainDB);
+ try {
+ mHearingAid.setVolume(gainDB);
+ } catch (Exception e) {
+ Log.i(TAG, "Exception while setting hearing aid volume", e);
+ }
}
/*package*/ synchronized void onBroadcastScoConnectionState(int state) {
@@ -471,7 +488,7 @@
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@@ -486,6 +503,35 @@
mBluetoothHeadset = null;
}
+ //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ /*package*/ synchronized void onBtProfileDisconnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dp = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAid = null;
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudio = null;
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ // shouldn't be received here as profile doesn't involve BtHelper
+ Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper "
+ + BluetoothProfile.getProfileName(profile));
+ break;
+
+ default:
+ // Not a valid profile to disconnect
+ Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
+ + BluetoothProfile.getProfileName(profile));
+ break;
+ }
+ }
+
+ @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
onHeadsetProfileConnected((BluetoothHeadset) proxy);
@@ -671,7 +717,6 @@
public void onServiceConnected(int profile, BluetoothProfile proxy) {
switch(profile) {
case BluetoothProfile.A2DP:
- case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.HEADSET:
case BluetoothProfile.HEARING_AID:
case BluetoothProfile.LE_AUDIO:
@@ -681,6 +726,10 @@
mDeviceBroker.postBtProfileConnected(profile, proxy);
break;
+ case BluetoothProfile.A2DP_SINK:
+ // no A2DP sink functionality handled by BtHelper
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ // no broadcast functionality handled by BtHelper
default:
break;
}
@@ -689,14 +738,19 @@
switch (profile) {
case BluetoothProfile.A2DP:
- case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.HEADSET:
case BluetoothProfile.HEARING_AID:
case BluetoothProfile.LE_AUDIO:
- case BluetoothProfile.LE_AUDIO_BROADCAST:
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "BT profile service: disconnecting "
+ + BluetoothProfile.getProfileName(profile) + " profile"));
mDeviceBroker.postBtProfileDisconnected(profile);
break;
+ case BluetoothProfile.A2DP_SINK:
+ // no A2DP sink functionality handled by BtHelper
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ // no broadcast functionality handled by BtHelper
default:
break;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index 0b5c1c1..969a174 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -150,13 +150,6 @@
}
/**
- * Returns if the sensor is side fps.
- */
- public boolean isSfps() {
- return mSidefpsController.isPresent();
- }
-
- /**
* Consumer for a biometric overlay controller.
*
* This behaves like a normal {@link Consumer} except that it will trap and log
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 932c0b4..a90679e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -236,14 +236,8 @@
@Override
public void onError(int errorCode, int vendorCode) {
- if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR
- && vendorCode == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
- && mSensorOverlays.isSfps()) {
- super.onError(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED,
- 0 /* vendorCode */);
- } else {
- super.onError(errorCode, vendorCode);
- }
+ super.onError(errorCode, vendorCode);
+
if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) {
BiometricNotificationUtils.showBadCalibrationNotification(getContext());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index cf54662..513b3e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -146,14 +146,7 @@
}
});
mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR
- && vendorCode == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
- && mSensorOverlays.isSfps()) {
- super.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
- 0 /* vendorCode */);
- } else {
- super.onAcquired(acquiredInfo, vendorCode);
- }
+ super.onAcquired(acquiredInfo, vendorCode);
}
@Override
@@ -281,5 +274,8 @@
}
@Override
- public void onPowerPressed() { }
+ public void onPowerPressed() {
+ onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
+ 0 /* vendorCode */);
+ }
}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 001cb10..a335338 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.sensor.VirtualSensor;
import android.os.LocaleList;
import android.util.ArraySet;
@@ -78,6 +79,15 @@
public abstract int getDeviceOwnerUid(int deviceId);
/**
+ * Returns the VirtualSensor for the given deviceId and sensor handle, if any.
+ *
+ * @param deviceId the virtual device that owns the sensor
+ * @param handle the sensor handle
+ * @return the VirtualSensor with the given handle, or {@code null} if no such sensor exists.
+ */
+ public abstract @Nullable VirtualSensor getVirtualSensor(int deviceId, int handle);
+
+ /**
* Finds VirtualDevices where an app is running.
*
* @param uid - the app's uid
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index f229d0f..a1081b2 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -37,6 +37,7 @@
private static native void nativeSetHdrConversionMode(int conversionMode,
int preferredHdrOutputType, int[] autoHdrTypes, int autoHdrTypesLength);
private static native int[] nativeGetSupportedHdrOutputTypes();
+ private static native boolean nativeGetHdrOutputConversionSupport();
/**
* Create a display in SurfaceFlinger.
@@ -118,4 +119,11 @@
public static @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypes() {
return nativeGetSupportedHdrOutputTypes();
}
+
+ /**
+ * @hide
+ */
+ public static boolean getHdrOutputConversionSupport() {
+ return nativeGetHdrOutputConversionSupport();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c0c286b..3c9af13 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -450,7 +450,7 @@
// so -2 is used instead
private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
- private static final float NITS_INVALID = -1;
+ static final float NITS_INVALID = -1;
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
@@ -840,7 +840,7 @@
/**
* Calculates the nits value for the specified backlight value if a mapping exists.
*
- * @return The mapped nits or 0 if no mapping exits.
+ * @return The mapped nits or {@link #NITS_INVALID} if no mapping exits.
*/
public float getNitsFromBacklight(float backlight) {
if (mBacklightToNitsSpline == null) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index b7b7031..213ee64 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -226,6 +226,16 @@
public static final int DIFF_COLOR_MODE = 1 << 2;
/**
+ * Diff result: The hdr/sdr ratio differs
+ */
+ public static final int DIFF_HDR_SDR_RATIO = 1 << 3;
+
+ /**
+ * Diff result: Catch-all for "everything changed"
+ */
+ public static final int DIFF_EVERYTHING = 0XFFFFFFFF;
+
+ /**
* Gets the name of the display device, which may be derived from EDID or
* other sources. The name may be localized and displayed to the user.
*/
@@ -414,6 +424,9 @@
public float brightnessMaximum;
public float brightnessDefault;
+ // NaN means unsupported
+ public float hdrSdrRatio = Float.NaN;
+
/**
* Install orientation of display panel relative to its natural orientation.
*/
@@ -449,6 +462,9 @@
if (colorMode != other.colorMode) {
diff |= DIFF_COLOR_MODE;
}
+ if (!BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio)) {
+ diff |= DIFF_HDR_SDR_RATIO;
+ }
if (!Objects.equals(name, other.name)
|| !Objects.equals(uniqueId, other.uniqueId)
|| width != other.width
@@ -527,6 +543,7 @@
brightnessMinimum = other.brightnessMinimum;
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
+ hdrSdrRatio = other.hdrSdrRatio;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
displayShape = other.displayShape;
@@ -575,6 +592,7 @@
sb.append(", brightnessMinimum ").append(brightnessMinimum);
sb.append(", brightnessMaximum ").append(brightnessMaximum);
sb.append(", brightnessDefault ").append(brightnessDefault);
+ sb.append(", hdrSdrRatio ").append(hdrSdrRatio);
if (roundedCorners != null) {
sb.append(", roundedCorners ").append(roundedCorners);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 33a63a9..ea52a3d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -42,7 +42,6 @@
private static final Boolean DEBUG = false;
public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
- public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
/**
@@ -83,15 +82,15 @@
Trace.beginAsyncSection(tag, 0);
}
switch (event) {
- case DISPLAY_DEVICE_EVENT_ADDED:
+ case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
handleDisplayDeviceAdded(device);
break;
- case DISPLAY_DEVICE_EVENT_CHANGED:
+ case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
handleDisplayDeviceChanged(device);
break;
- case DISPLAY_DEVICE_EVENT_REMOVED:
+ case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
handleDisplayDeviceRemoved(device);
break;
}
@@ -174,7 +173,7 @@
if (diff == DisplayDeviceInfo.DIFF_STATE) {
Slog.i(TAG, "Display device changed state: \"" + info.name
+ "\", " + Display.stateToString(info.state));
- } else if (diff != 0) {
+ } else if (diff != DisplayDeviceInfo.DIFF_HDR_SDR_RATIO) {
Slog.i(TAG, "Display device changed: " + info);
}
@@ -188,7 +187,7 @@
device.mDebugLastLoggedDeviceInfo = info;
device.applyPendingDisplayDeviceInfoChangesLocked();
- sendEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
+ sendChangedEventLocked(device, diff);
if (DEBUG) {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -216,12 +215,22 @@
}
}
+ @GuardedBy("mSyncRoot")
+ private void sendChangedEventLocked(DisplayDevice device, int diff) {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).onDisplayDeviceChangedLocked(device, diff);
+ }
+ }
+
/**
* Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}.
*/
public interface Listener {
void onDisplayDeviceEventLocked(DisplayDevice device, int event);
+ void onDisplayDeviceChangedLocked(DisplayDevice device, int diff);
+
// TODO: multi-display - Try to remove the need for requestTraversal...it feels like
// a shoe-horned method for a shoe-horned feature.
void onTraversalRequested();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e200d12..9f1007d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -247,6 +247,8 @@
// HDR conversion mode chosen by user
@GuardedBy("mSyncRoot")
private HdrConversionMode mHdrConversionMode = null;
+ // Actual HDR conversion mode, which takes app overrides into account.
+ private HdrConversionMode mOverrideHdrConversionMode = null;
// The synchronization root for the display manager.
// This lock guards most of the display manager's state.
@@ -1745,6 +1747,10 @@
mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS);
}
+ private void handleLogicalDisplayHdrSdrRatioChangedLocked(@NonNull LogicalDisplay display) {
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED);
+ }
+
private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
.mDisplayDeviceConfig);
@@ -2025,12 +2031,23 @@
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
}
- mInjector.setHdrConversionMode(hdrConversionMode.getConversionMode(),
- hdrConversionMode.getPreferredHdrOutputType(), autoHdrOutputTypes);
+
+ if (!mInjector.getHdrOutputConversionSupport()) {
+ return;
+ }
+ // If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then
+ // set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH.
+ if (mOverrideHdrConversionMode == null) {
+ mInjector.setHdrConversionMode(hdrConversionMode.getConversionMode(),
+ hdrConversionMode.getPreferredHdrOutputType(), autoHdrOutputTypes);
+ } else {
+ mInjector.setHdrConversionMode(mOverrideHdrConversionMode.getConversionMode(),
+ mOverrideHdrConversionMode.getPreferredHdrOutputType(), null);
+ }
}
}
- private HdrConversionMode getHdrConversionModeInternal() {
+ private HdrConversionMode getHdrConversionModeSettingInternal() {
synchronized (mSyncRoot) {
if (mHdrConversionMode != null) {
return mHdrConversionMode;
@@ -2039,6 +2056,16 @@
return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM);
}
+ private HdrConversionMode getHdrConversionModeInternal() {
+ HdrConversionMode mode;
+ synchronized (mSyncRoot) {
+ mode = mOverrideHdrConversionMode != null
+ ? mOverrideHdrConversionMode
+ : mHdrConversionMode;
+ }
+ return mode != null ? mode : new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM);
+ }
+
private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
if (mSupportedHdrOutputType == null) {
mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
@@ -2181,7 +2208,7 @@
private void setDisplayPropertiesInternal(int displayId, boolean hasContent,
float requestedRefreshRate, int requestedModeId, float requestedMinRefreshRate,
float requestedMaxRefreshRate, boolean preferMinimalPostProcessing,
- boolean inTraversal) {
+ boolean disableHdrConversion, boolean inTraversal) {
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display == null) {
@@ -2226,6 +2253,20 @@
if (shouldScheduleTraversal) {
scheduleTraversalLocked(inTraversal);
}
+
+ if (mHdrConversionMode == null) {
+ return;
+ }
+ if (mOverrideHdrConversionMode == null && disableHdrConversion) {
+ mOverrideHdrConversionMode =
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
+ setHdrConversionModeInternal(mHdrConversionMode);
+ handleLogicalDisplayChangedLocked(display);
+ } else if (mOverrideHdrConversionMode != null && !disableHdrConversion) {
+ mOverrideHdrConversionMode = null;
+ setHdrConversionModeInternal(mHdrConversionMode);
+ handleLogicalDisplayChangedLocked(display);
+ }
}
}
@@ -2783,6 +2824,10 @@
int[] getSupportedHdrOutputTypes() {
return DisplayControl.getSupportedHdrOutputTypes();
}
+
+ boolean getHdrOutputConversionSupport() {
+ return DisplayControl.getHdrOutputConversionSupport();
+ }
}
@VisibleForTesting
@@ -2983,6 +3028,10 @@
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
handleLogicalDisplayDeviceStateTransitionLocked(display);
break;
+
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED:
+ handleLogicalDisplayHdrSdrRatioChangedLocked(display);
+ break;
}
}
@@ -3052,6 +3101,8 @@
return (mask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
+ case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
+ return (mask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0;
default:
// This should never happen.
Slog.e(TAG, "Unknown display event " + event);
@@ -3757,6 +3808,16 @@
}
@Override // Binder call
+ public HdrConversionMode getHdrConversionModeSetting() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getHdrConversionModeSettingInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public HdrConversionMode getHdrConversionMode() {
final long token = Binder.clearCallingIdentity();
try {
@@ -4030,10 +4091,10 @@
public void setDisplayProperties(int displayId, boolean hasContent,
float requestedRefreshRate, int requestedMode, float requestedMinRefreshRate,
float requestedMaxRefreshRate, boolean requestedMinimalPostProcessing,
- boolean inTraversal) {
+ boolean disableHdrConversion, boolean inTraversal) {
setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate,
requestedMode, requestedMinRefreshRate, requestedMaxRefreshRate,
- requestedMinimalPostProcessing, inTraversal);
+ requestedMinimalPostProcessing, disableHdrConversion, inTraversal);
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1305d63..f5ca8aa 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -138,6 +138,7 @@
private static final int MSG_UPDATE_RBC = 11;
private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
+ private static final int MSG_SWITCH_USER = 14;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -714,6 +715,11 @@
@Override
public void onSwitchUser(@UserIdInt int newUserId) {
+ Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
+ mHandler.sendMessage(msg);
+ }
+
+ private void handleOnSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
@@ -3073,6 +3079,10 @@
case MSG_STATSD_HBM_BRIGHTNESS:
logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
break;
+
+ case MSG_SWITCH_USER:
+ handleOnSwitchUser(msg.arg1);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 82faa12..5667ddf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -135,6 +135,7 @@
private static final int MSG_UPDATE_RBC = 9;
private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
+ private static final int MSG_SWITCH_USER = 12;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
@@ -605,6 +606,11 @@
@Override
public void onSwitchUser(@UserIdInt int newUserId) {
+ Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
+ mHandler.sendMessage(msg);
+ }
+
+ private void handleOnSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
@@ -2573,6 +2579,10 @@
case MSG_STATSD_HBM_BRIGHTNESS:
logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
break;
+
+ case MSG_SWITCH_USER:
+ handleOnSwitchUser(msg.arg1);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index be5980b..8f52c97 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -204,6 +204,7 @@
// This is only set in the runnable returned from requestDisplayStateLocked.
private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ private float mCurrentHdrSdrRatio = Float.NaN;
private int mDefaultModeId = INVALID_MODE_ID;
private int mSystemPreferredModeId = INVALID_MODE_ID;
private int mDefaultModeGroup;
@@ -729,6 +730,7 @@
mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
+ mInfo.hdrSdrRatio = mCurrentHdrSdrRatio;
}
return mInfo;
}
@@ -840,12 +842,10 @@
private void setCommittedState(int state) {
// After the display state is set, let's update the committed state.
- getHandler().post(() -> {
- synchronized (getSyncRoot()) {
- mCommittedState = state;
- updateDeviceInfoLocked();
- }
- });
+ synchronized (getSyncRoot()) {
+ mCommittedState = state;
+ updateDeviceInfoLocked();
+ }
}
private void setDisplayBrightness(float brightnessState,
@@ -881,6 +881,9 @@
"SdrScreenBrightness",
BrightnessSynchronizer.brightnessFloatToInt(
sdrBrightnessState));
+
+ handleHdrSdrNitsChanged(nits, sdrNits);
+
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -897,6 +900,23 @@
private float backlightToNits(float backlight) {
return getDisplayDeviceConfig().getNitsFromBacklight(backlight);
}
+
+ void handleHdrSdrNitsChanged(float displayNits, float sdrNits) {
+ final float newHdrSdrRatio;
+ if (displayNits != DisplayDeviceConfig.NITS_INVALID
+ && sdrNits != DisplayDeviceConfig.NITS_INVALID) {
+ newHdrSdrRatio = displayNits / sdrNits;
+ } else {
+ newHdrSdrRatio = Float.NaN;
+ }
+ if (!BrightnessSynchronizer.floatEquals(
+ mCurrentHdrSdrRatio, newHdrSdrRatio)) {
+ synchronized (getSyncRoot()) {
+ mCurrentHdrSdrRatio = newHdrSdrRatio;
+ updateDeviceInfoLocked();
+ }
+ }
+ }
};
}
return null;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 667c8c8..2104ee3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -454,6 +454,7 @@
mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+ mBaseDisplayInfo.hdrSdrRatio = deviceInfo.hdrSdrRatio;
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index b48ba65..80e76b6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -71,6 +71,7 @@
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6;
+ public static final int LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED = 7;
public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
@@ -226,14 +227,6 @@
handleDisplayDeviceAddedLocked(device);
break;
- case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED:
- if (DEBUG) {
- Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
- }
- finishStateTransitionLocked(false /*force*/);
- updateLogicalDisplaysLocked();
- break;
-
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED:
if (DEBUG) {
Slog.d(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
@@ -245,6 +238,15 @@
}
@Override
+ public void onDisplayDeviceChangedLocked(DisplayDevice device, int diff) {
+ if (DEBUG) {
+ Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
+ }
+ finishStateTransitionLocked(false /*force*/);
+ updateLogicalDisplaysLocked(diff);
+ }
+
+ @Override
public void onTraversalRequested() {
mListener.onTraversalRequested();
}
@@ -655,13 +657,20 @@
}
}
+ private void updateLogicalDisplaysLocked() {
+ updateLogicalDisplaysLocked(DisplayDeviceInfo.DIFF_EVERYTHING);
+ }
+
/**
* Updates the rest of the display system once all the changes are applied for display
* devices and logical displays. The includes releasing invalid/empty LogicalDisplays,
* creating/adjusting/removing DisplayGroups, and notifying the rest of the system of the
* relevant changes.
+ *
+ * @param diff The DisplayDeviceInfo.DIFF_* of what actually changed to enable finer-grained
+ * display update listeners
*/
- private void updateLogicalDisplaysLocked() {
+ private void updateLogicalDisplaysLocked(int diff) {
// Go through all the displays and figure out if they need to be updated.
// Loops in reverse so that displays can be removed during the loop without affecting the
// rest of the loop.
@@ -715,7 +724,13 @@
} else if (!mTempDisplayInfo.equals(newDisplayInfo)) {
// FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
assignDisplayGroupLocked(display);
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+ // If only the hdr/sdr ratio changed, then send just the event for that case
+ if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
+ mLogicalDisplaysToUpdate.put(displayId,
+ LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
+ } else {
+ mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+ }
// The display is involved in a display layout transition
} else if (updateState == UPDATE_STATE_TRANSITION) {
@@ -774,6 +789,7 @@
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED);
@@ -1125,6 +1141,8 @@
return "swapped";
case LOGICAL_DISPLAY_EVENT_REMOVED:
return "removed";
+ case LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED:
+ return "hdr_sdr_ratio_changed";
}
return null;
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 172aa20..b2b22a0 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -50,6 +50,7 @@
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputSensorInfo;
+import android.hardware.input.InputSettings;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.TouchCalibration;
import android.hardware.lights.Light;
@@ -1309,7 +1310,7 @@
throw new SecurityException("Requires SET_POINTER_SPEED permission");
}
- if (speed < InputManager.MIN_POINTER_SPEED || speed > InputManager.MAX_POINTER_SPEED) {
+ if (speed < InputSettings.MIN_POINTER_SPEED || speed > InputSettings.MAX_POINTER_SPEED) {
throw new IllegalArgumentException("speed out of range");
}
@@ -1317,8 +1318,8 @@
}
private void setPointerSpeedUnchecked(int speed) {
- speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
- InputManager.MAX_POINTER_SPEED);
+ speed = Math.min(Math.max(speed, InputSettings.MIN_POINTER_SPEED),
+ InputSettings.MAX_POINTER_SPEED);
mNative.setPointerSpeed(speed);
}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index a113d01..5b21669 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -21,7 +21,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
-import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
@@ -32,7 +32,6 @@
import android.view.ViewConfiguration;
import java.util.Map;
-import java.util.Objects;
import java.util.function.Consumer;
/** Observes settings changes and propagates them to the native side. */
@@ -111,9 +110,9 @@
private int getPointerSpeedValue(String settingName) {
int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
- settingName, InputManager.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
- return Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
- InputManager.MAX_POINTER_SPEED);
+ settingName, InputSettings.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+ return Math.min(Math.max(speed, InputSettings.MIN_POINTER_SPEED),
+ InputSettings.MAX_POINTER_SPEED);
}
private void updateMousePointerSpeed() {
@@ -170,8 +169,7 @@
}
private void updateMaximumObscuringOpacityForTouch() {
- InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
- final float opacity = im.getMaximumObscuringOpacityForTouch();
+ final float opacity = InputSettings.getMaximumObscuringOpacityForTouch(mContext);
if (opacity < 0 || opacity > 1) {
Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
+ ", it should be >= 0 and <= 1, rejecting update.");
diff --git a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
index 3a25146..f5114b7 100644
--- a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
+++ b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
@@ -32,6 +32,14 @@
abstract class NetworkTimeHelper {
/**
+ * This compile-time value can be changed to switch between new and old ways to obtain network
+ * time for GNSS. If you have to turn this from {@code true} to {@code false} then please create
+ * a platform bug. This switch will be removed in a future release. If there are problems with
+ * the new impl we'd like to hear about them.
+ */
+ static final boolean USE_TIME_DETECTOR_IMPL = false;
+
+ /**
* The callback interface used by {@link NetworkTimeHelper} to report the time to {@link
* GnssLocationProvider}. The callback can happen at any time using the thread associated with
* the looper passed to {@link #create(Context, Looper, InjectTimeCallback)}.
@@ -47,7 +55,13 @@
static NetworkTimeHelper create(
@NonNull Context context, @NonNull Looper looper,
@NonNull InjectTimeCallback injectTimeCallback) {
- return new NtpNetworkTimeHelper(context, looper, injectTimeCallback);
+ if (USE_TIME_DETECTOR_IMPL) {
+ TimeDetectorNetworkTimeHelper.Environment environment =
+ new TimeDetectorNetworkTimeHelper.EnvironmentImpl(looper);
+ return new TimeDetectorNetworkTimeHelper(environment, injectTimeCallback);
+ } else {
+ return new NtpNetworkTimeHelper(context, looper, injectTimeCallback);
+ }
}
/**
@@ -74,7 +88,9 @@
* Notifies that network connectivity has been established.
*
* <p>Called by {@link GnssLocationProvider} when the device establishes a data network
- * connection.
+ * connection. This call should be removed eventually because it should be handled by the {@link
+ * NetworkTimeHelper} implementation itself, but has been retained for compatibility while
+ * switching implementations.
*/
abstract void onNetworkAvailable();
@@ -82,4 +98,5 @@
* Dumps internal state during bugreports useful for debugging.
*/
abstract void dump(@NonNull PrintWriter pw);
+
}
diff --git a/services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java
new file mode 100644
index 0000000..15366d3
--- /dev/null
+++ b/services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.timedetector.NetworkTimeSuggestion;
+import com.android.server.timedetector.TimeDetectorInternal;
+import com.android.server.timezonedetector.StateChangeListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Handles injecting network time to GNSS by using information from the platform time detector.
+ */
+public class TimeDetectorNetworkTimeHelper extends NetworkTimeHelper {
+
+ /** Returns {@code true} if the TimeDetectorNetworkTimeHelper is being used. */
+ public static boolean isInUse() {
+ return NetworkTimeHelper.USE_TIME_DETECTOR_IMPL;
+ }
+
+ /**
+ * An interface exposed for easier testing that the surrounding class uses for interacting with
+ * platform services, handlers, etc.
+ */
+ interface Environment {
+
+ /**
+ * Returns the current elapsed realtime value. The same as calling {@link
+ * SystemClock#elapsedRealtime()} but easier to fake in tests.
+ */
+ @ElapsedRealtimeLong long elapsedRealtimeMillis();
+
+ /**
+ * Returns the latest / best network time available from the time detector service.
+ */
+ @Nullable NetworkTimeSuggestion getLatestNetworkTime();
+
+ /**
+ * Sets a listener that will receive a callback when the value returned by {@link
+ * #getLatestNetworkTime()} has changed.
+ */
+ void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener);
+
+ /**
+ * Requests asynchronous execution of {@link
+ * TimeDetectorNetworkTimeHelper#queryAndInjectNetworkTime}, to execute as soon as possible.
+ * The thread used is the same as used by {@link #requestDelayedTimeQueryCallback}.
+ * Only one immediate callback can be requested at a time; requesting a new immediate
+ * callback will clear any previously requested one.
+ */
+ void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, String reason);
+
+ /**
+ * Requests a delayed call to
+ * {@link TimeDetectorNetworkTimeHelper#delayedQueryAndInjectNetworkTime()}.
+ * The thread used is the same as used by {@link #requestImmediateTimeQueryCallback}.
+ * Only one delayed callback can be scheduled at a time; requesting a new delayed callback
+ * will clear any previously requested one.
+ */
+ void requestDelayedTimeQueryCallback(
+ TimeDetectorNetworkTimeHelper helper, @DurationMillisLong long delayMillis);
+
+ /**
+ * Clear a delayed time query callback. This has no effect if no delayed callback is
+ * currently set.
+ */
+ void clearDelayedTimeQueryCallback();
+ }
+
+ private static final String TAG = "TDNetworkTimeHelper";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /** The maximum age of a network time signal that will be passed to GNSS. */
+ @VisibleForTesting
+ static final int MAX_NETWORK_TIME_AGE_MILLIS = 24 * 60 * 60 * 1000;
+
+ /**
+ * The maximum time that is allowed to pass before a network time signal should be evaluated to
+ * be passed to GNSS when mOnDemandTimeInjection == false.
+ */
+ static final int NTP_REFRESH_INTERVAL_MILLIS = MAX_NETWORK_TIME_AGE_MILLIS;
+
+ private final LocalLog mDumpLog = new LocalLog(10, /*useLocalTimestamps=*/false);
+
+ /** The object the helper uses to interact with other components. */
+ @NonNull private final Environment mEnvironment;
+ @NonNull private final InjectTimeCallback mInjectTimeCallback;
+
+ /** Set to true if the GNSS engine requested on-demand NTP time injections. */
+ @GuardedBy("this")
+ private boolean mPeriodicTimeInjectionEnabled;
+
+ /**
+ * Set to true when a network time has been injected. Used to ensure that a network time is
+ * injected if this object wasn't listening when a network time signal first became available.
+ */
+ @GuardedBy("this")
+ private boolean mNetworkTimeInjected;
+
+ TimeDetectorNetworkTimeHelper(
+ @NonNull Environment environment, @NonNull InjectTimeCallback injectTimeCallback) {
+ mInjectTimeCallback = Objects.requireNonNull(injectTimeCallback);
+ mEnvironment = Objects.requireNonNull(environment);
+
+ // Start listening for new network time updates immediately.
+ mEnvironment.setNetworkTimeUpdateListener(this::onNetworkTimeAvailable);
+ }
+
+ @Override
+ synchronized void setPeriodicTimeInjectionMode(boolean periodicTimeInjectionEnabled) {
+ // Periodic time injection has a complicated history. See b/73893222. When it is true, it
+ // doesn't mean ONLY send it periodically.
+ //
+ // periodicTimeInjectionEnabled == true means the GNSS would like to be told the time
+ // periodically in addition to all the other triggers (e.g. network available).
+
+ mPeriodicTimeInjectionEnabled = periodicTimeInjectionEnabled;
+ if (!periodicTimeInjectionEnabled) {
+ // Cancel any previously scheduled periodic query.
+ removePeriodicNetworkTimeQuery();
+ }
+
+ // Inject the latest network time in all cases if it is available.
+ // Calling queryAndInjectNetworkTime() will cause a time signal to be injected if one is
+ // available AND will cause the next periodic query to be scheduled.
+ String reason = "setPeriodicTimeInjectionMode(" + periodicTimeInjectionEnabled + ")";
+ mEnvironment.requestImmediateTimeQueryCallback(this, reason);
+ }
+
+ void onNetworkTimeAvailable() {
+ // A new network time could become available at any time. Make sure it is passed to GNSS.
+ mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkTimeAvailable");
+ }
+
+ @Override
+ void onNetworkAvailable() {
+ // In the original NetworkTimeHelper implementation, onNetworkAvailable() would cause an NTP
+ // refresh to be made if it had previously been blocked by network issues. This
+ // implementation generally relies on components associated with the time detector to
+ // monitor the network and call onNetworkTimeAvailable() when a time is available. However,
+ // it also checks mNetworkTimeInjected in case this component wasn't listening for
+ // onNetworkTimeAvailable() when the last one became available.
+ synchronized (this) {
+ if (!mNetworkTimeInjected) {
+ // Guard against ordering issues: This check should ensure that if a network time
+ // became available before this class started listening then the initial network
+ // time will still be injected.
+ mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkAvailable");
+ }
+ }
+ }
+
+ @Override
+ void demandUtcTimeInjection() {
+ mEnvironment.requestImmediateTimeQueryCallback(this, "demandUtcTimeInjection");
+ }
+
+ // This method should always be invoked on the mEnvironment thread.
+ void delayedQueryAndInjectNetworkTime() {
+ queryAndInjectNetworkTime("delayedTimeQueryCallback");
+ }
+
+ // This method should always be invoked on the mEnvironment thread.
+ synchronized void queryAndInjectNetworkTime(@NonNull String reason) {
+ NetworkTimeSuggestion latestNetworkTime = mEnvironment.getLatestNetworkTime();
+
+ maybeInjectNetworkTime(latestNetworkTime, reason);
+
+ // Deschedule (if needed) any previously scheduled periodic query.
+ removePeriodicNetworkTimeQuery();
+
+ if (mPeriodicTimeInjectionEnabled) {
+ int maxDelayMillis = NTP_REFRESH_INTERVAL_MILLIS;
+ String debugMsg = "queryAndInjectNtpTime: Scheduling periodic query"
+ + " reason=" + reason
+ + " latestNetworkTime=" + latestNetworkTime
+ + " maxDelayMillis=" + maxDelayMillis;
+ logToDumpLog(debugMsg);
+
+ // GNSS is expecting periodic injections, so schedule the next one.
+ mEnvironment.requestDelayedTimeQueryCallback(this, maxDelayMillis);
+ }
+ }
+
+ private long calculateTimeSignalAgeMillis(
+ @Nullable NetworkTimeSuggestion networkTimeSuggestion) {
+ if (networkTimeSuggestion == null) {
+ return Long.MAX_VALUE;
+ }
+
+ long suggestionElapsedRealtimeMillis =
+ networkTimeSuggestion.getUnixEpochTime().getElapsedRealtimeMillis();
+ long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+ return currentElapsedRealtimeMillis - suggestionElapsedRealtimeMillis;
+ }
+
+ @GuardedBy("this")
+ private void maybeInjectNetworkTime(
+ @Nullable NetworkTimeSuggestion latestNetworkTime, @NonNull String reason) {
+ // Historically, time would only be injected if it was under a certain age. This has been
+ // kept in case it is assumed by GNSS implementations.
+ if (calculateTimeSignalAgeMillis(latestNetworkTime) > MAX_NETWORK_TIME_AGE_MILLIS) {
+ String debugMsg = "maybeInjectNetworkTime: Not injecting latest network time"
+ + " latestNetworkTime=" + latestNetworkTime
+ + " reason=" + reason;
+ logToDumpLog(debugMsg);
+ return;
+ }
+
+ UnixEpochTime unixEpochTime = latestNetworkTime.getUnixEpochTime();
+ long unixEpochTimeMillis = unixEpochTime.getUnixEpochTimeMillis();
+ long currentTimeMillis = System.currentTimeMillis();
+ String debugMsg = "maybeInjectNetworkTime: Injecting latest network time"
+ + " latestNetworkTime=" + latestNetworkTime
+ + " reason=" + reason
+ + " System time offset millis=" + (unixEpochTimeMillis - currentTimeMillis);
+ logToDumpLog(debugMsg);
+
+ long timeReferenceMillis = unixEpochTime.getElapsedRealtimeMillis();
+ int uncertaintyMillis = latestNetworkTime.getUncertaintyMillis();
+ mInjectTimeCallback.injectTime(unixEpochTimeMillis, timeReferenceMillis, uncertaintyMillis);
+ mNetworkTimeInjected = true;
+ }
+
+ @Override
+ void dump(@NonNull PrintWriter pw) {
+ pw.println("TimeDetectorNetworkTimeHelper:");
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.increaseIndent();
+ synchronized (this) {
+ ipw.println("mPeriodicTimeInjectionEnabled=" + mPeriodicTimeInjectionEnabled);
+ }
+
+ ipw.println("Debug log:");
+ mDumpLog.dump(ipw);
+ }
+
+ private void logToDumpLog(@NonNull String message) {
+ mDumpLog.log(message);
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+
+ private void removePeriodicNetworkTimeQuery() {
+ // De-schedule any previously scheduled refresh. This is idempotent and has no effect if
+ // there isn't one.
+ mEnvironment.clearDelayedTimeQueryCallback();
+ }
+
+ /** The real implementation of {@link Environment} used outside of tests. */
+ static class EnvironmentImpl implements Environment {
+
+ /** Used to ensure one scheduled runnable is queued at a time. */
+ private final Object mScheduledRunnableToken = new Object();
+ /** Used to ensure one immediate runnable is queued at a time. */
+ private final Object mImmediateRunnableToken = new Object();
+ private final Handler mHandler;
+ private final TimeDetectorInternal mTimeDetectorInternal;
+
+ EnvironmentImpl(Looper looper) {
+ mHandler = new Handler(looper);
+ mTimeDetectorInternal = LocalServices.getService(TimeDetectorInternal.class);
+ }
+
+ @Override
+ public long elapsedRealtimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public NetworkTimeSuggestion getLatestNetworkTime() {
+ return mTimeDetectorInternal.getLatestNetworkSuggestion();
+ }
+
+ @Override
+ public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) {
+ mTimeDetectorInternal.addNetworkTimeUpdateListener(stateChangeListener);
+ }
+
+ @Override
+ public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
+ String reason) {
+ // Ensure only one immediate callback is scheduled at a time. There's no
+ // post(Runnable, Object), so we postDelayed() with a zero wait.
+ synchronized (this) {
+ mHandler.removeCallbacksAndMessages(mImmediateRunnableToken);
+ mHandler.postDelayed(() -> helper.queryAndInjectNetworkTime(reason),
+ mImmediateRunnableToken, 0);
+ }
+ }
+
+ @Override
+ public void requestDelayedTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
+ long delayMillis) {
+ synchronized (this) {
+ clearDelayedTimeQueryCallback();
+ mHandler.postDelayed(helper::delayedQueryAndInjectNetworkTime,
+ mScheduledRunnableToken, delayMillis);
+ }
+ }
+
+ @Override
+ public synchronized void clearDelayedTimeQueryCallback() {
+ mHandler.removeCallbacksAndMessages(mScheduledRunnableToken);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 25efe0c..77b9abe 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -287,19 +287,16 @@
if (packageName == null || packageName.isEmpty()) {
throw new IllegalArgumentException("package name must not be empty");
}
-
- final UserHandle callingUser = Binder.getCallingUserHandle();
- final long callingToken = Binder.clearCallingIdentity();
+ final ApplicationInfo ai;
+ try {
+ ai = mPackageManager.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("No package matching :" + packageName);
+ }
MediaProjection projection;
+ final long callingToken = Binder.clearCallingIdentity();
try {
- ApplicationInfo ai;
- try {
- ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);
- } catch (NameNotFoundException e) {
- throw new IllegalArgumentException("No package matching :" + packageName);
- }
-
projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
ai.isPrivilegedApp());
if (isPermanentGrant) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c1b3834..fd9f8d1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -393,12 +393,17 @@
static final int INVALID_UID = -1;
static final String ROOT_PKG = "root";
- static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
+ static final String[] ALLOWED_ADJUSTMENTS = new String[] {
+ Adjustment.KEY_PEOPLE,
+ Adjustment.KEY_SNOOZE_CRITERIA,
+ Adjustment.KEY_USER_SENTIMENT,
Adjustment.KEY_CONTEXTUAL_ACTIONS,
Adjustment.KEY_TEXT_REPLIES,
- Adjustment.KEY_NOT_CONVERSATION,
Adjustment.KEY_IMPORTANCE,
- Adjustment.KEY_RANKING_SCORE
+ Adjustment.KEY_IMPORTANCE_PROPOSAL,
+ Adjustment.KEY_SENSITIVE_CONTENT,
+ Adjustment.KEY_RANKING_SCORE,
+ Adjustment.KEY_NOT_CONVERSATION
};
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
@@ -2567,27 +2572,6 @@
for (String name : properties.getKeyset()) {
if (SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE.equals(name)) {
mAssistants.resetDefaultAssistantsIfNecessary();
- } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_PRIORITIZER.equals(name)) {
- String value = properties.getString(name, null);
- if ("true".equals(value)) {
- mAssistants.allowAdjustmentType(Adjustment.KEY_IMPORTANCE);
- } else if ("false".equals(value)) {
- mAssistants.disallowAdjustmentType(Adjustment.KEY_IMPORTANCE);
- }
- } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_RANKING.equals(name)) {
- String value = properties.getString(name, null);
- if ("true".equals(value)) {
- mAssistants.allowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
- } else if ("false".equals(value)) {
- mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
- }
- } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_NOT_CONVERSATION.equals(name)) {
- String value = properties.getString(name, null);
- if ("true".equals(value)) {
- mAssistants.allowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
- } else if ("false".equals(value)) {
- mAssistants.disallowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
- }
} else if (SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED.equals(name)) {
String value = properties.getString(name, null);
if ("true".equals(value)) {
@@ -4256,22 +4240,6 @@
return mAssistants.getAllowedAssistantAdjustments();
}
- @Override
- public void allowAssistantAdjustment(String adjustmentType) {
- checkCallerIsSystemOrSystemUiOrShell();
- mAssistants.allowAdjustmentType(adjustmentType);
-
- handleSavePolicyFile();
- }
-
- @Override
- public void disallowAssistantAdjustment(String adjustmentType) {
- checkCallerIsSystemOrSystemUiOrShell();
- mAssistants.disallowAdjustmentType(adjustmentType);
-
- handleSavePolicyFile();
- }
-
/**
* @deprecated Use {@link #getActiveNotificationsWithAttribution(String, String)} instead.
*/
@@ -10146,8 +10114,6 @@
public class NotificationAssistants extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
- private static final String TAG_ALLOWED_ADJUSTMENT_TYPES_OLD = "q_allowed_adjustments";
- private static final String TAG_ALLOWED_ADJUSTMENT_TYPES = "s_allowed_adjustments";
private static final String ATT_TYPES = "types";
private final Object mLock = new Object();
@@ -10224,10 +10190,9 @@
IPackageManager pm) {
super(context, lock, up, pm);
- // Add all default allowed adjustment types. Will be overwritten by values in xml,
- // if they exist
- for (int i = 0; i < DEFAULT_ALLOWED_ADJUSTMENTS.length; i++) {
- mAllowedAdjustments.add(DEFAULT_ALLOWED_ADJUSTMENTS[i]);
+ // Add all default allowed adjustment types.
+ for (int i = 0; i < ALLOWED_ADJUSTMENTS.length; i++) {
+ mAllowedAdjustments.add(ALLOWED_ADJUSTMENTS[i]);
}
}
@@ -10285,52 +10250,6 @@
return android.Manifest.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE;
}
- @Override
- protected void writeExtraXmlTags(TypedXmlSerializer out) throws IOException {
- synchronized (mLock) {
- out.startTag(null, TAG_ALLOWED_ADJUSTMENT_TYPES);
- out.attribute(null, ATT_TYPES, TextUtils.join(",", mAllowedAdjustments));
- out.endTag(null, TAG_ALLOWED_ADJUSTMENT_TYPES);
- }
- }
-
- @Override
- protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {
- if (TAG_ALLOWED_ADJUSTMENT_TYPES_OLD.equals(tag)
- || TAG_ALLOWED_ADJUSTMENT_TYPES.equals(tag)) {
- final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES);
- synchronized (mLock) {
- mAllowedAdjustments.clear();
- if (!TextUtils.isEmpty(types)) {
- mAllowedAdjustments.addAll(Arrays.asList(types.split(",")));
- }
- if (TAG_ALLOWED_ADJUSTMENT_TYPES_OLD.equals(tag)) {
- if (DEBUG) Slog.d(TAG, "Migrate allowed adjustments.");
- mAllowedAdjustments.addAll(
- Arrays.asList(DEFAULT_ALLOWED_ADJUSTMENTS));
- }
- }
- }
- }
-
- protected void allowAdjustmentType(String type) {
- synchronized (mLock) {
- mAllowedAdjustments.add(type);
- }
- for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
- mHandler.post(() -> notifyCapabilitiesChanged(info));
- }
- }
-
- protected void disallowAdjustmentType(String type) {
- synchronized (mLock) {
- mAllowedAdjustments.remove(type);
- }
- for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
- mHandler.post(() -> notifyCapabilitiesChanged(info));
- }
- }
-
protected List<String> getAllowedAssistantAdjustments() {
synchronized (mLock) {
List<String> types = new ArrayList<>();
@@ -10402,15 +10321,6 @@
mIsUserChanged.put(userId, set);
}
- private void notifyCapabilitiesChanged(final ManagedServiceInfo info) {
- final INotificationListener assistant = (INotificationListener) info.service;
- try {
- assistant.onAllowedAdjustmentsChanged();
- } catch (RemoteException ex) {
- Slog.e(TAG, "unable to notify assistant (capabilities): " + info, ex);
- }
- }
-
private void notifySeen(final ManagedServiceInfo info,
final ArrayList<String> keys) {
final INotificationListener assistant = (INotificationListener) info.service;
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 3799193..84ea077 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -320,12 +320,26 @@
MOBILE_NETWORK_SETTINGS);
}
+ /** Call intent with tel scheme */
+ private static final DefaultCrossProfileIntentFilter CALL =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ SKIP_CURRENT_PROFILE,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_CALL)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addDataScheme("tel")
+ .build();
+
/**
* Returns default telephony related intent filters for managed profile.
*/
public static List<DefaultCrossProfileIntentFilter> getDefaultManagedProfileTelephonyFilters() {
return Arrays.asList(
DIAL_DATA,
+ DIAL_MIME,
+ DIAL_RAW,
+ CALL,
SMS_MMS);
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index d8bfa59..1bd5b99 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -566,7 +566,10 @@
mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options);
}
- public void forceDexOpt(@NonNull Computer snapshot, String packageName) {
+ /** @deprecated For legacy shell command only. */
+ @Deprecated
+ public void forceDexOpt(@NonNull Computer snapshot, String packageName)
+ throws LegacyDexoptDisabledException {
PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
@@ -586,19 +589,7 @@
getDefaultCompilerFilter(), null /* splitName */,
DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
- @DexOptResult int res;
- if (useArtService()) {
- // performDexOptWithArtService ignores the snapshot and takes its own, so it can race
- // with the package checks above, but at worst the effect is only a bit less friendly
- // error below.
- res = performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
- } else {
- try {
- res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ @DexOptResult int res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index d4e3549..d39cac0 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -303,12 +303,6 @@
@Override
@Deprecated
- public final void forceDexOpt(String packageName) {
- mDexOptHelper.forceDexOpt(snapshot(), packageName);
- }
-
- @Override
- @Deprecated
public final ActivityInfo getActivityInfo(ComponentName component,
@PackageManager.ComponentInfoFlagsBits long flags, int userId) {
return snapshot().getActivityInfo(component, flags, userId);
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index f989f6f..c3cc392 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -153,7 +153,7 @@
mInstallSource = installSource;
mVolumeUuid = sessionParams.volumeUuid;
mPackageAbiOverride = sessionParams.abiOverride;
- mPermissionStates = sessionParams.getFinalPermissionStates();
+ mPermissionStates = sessionParams.getPermissionStates();
mAllowlistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions;
mAutoRevokePermissionsMode = sessionParams.autoRevokePermissionsMode;
mSigningDetails = signingDetails;
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index e254300..85d2df3 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -388,7 +388,7 @@
if (abi32 >= 0) {
final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
if (abi64 >= 0) {
- if (pkg.isUse32BitAbi()) {
+ if (pkg.is32BitAbiPreferred()) {
secondaryCpuAbi = primaryCpuAbi;
primaryCpuAbi = abi;
} else {
@@ -474,7 +474,8 @@
private boolean shouldExtractLibs(AndroidPackage pkg, boolean isSystemApp,
boolean isUpdatedSystemApp) {
// We shouldn't extract libs if the package is a library or if extractNativeLibs=false
- boolean extractLibs = !AndroidPackageUtils.isLibrary(pkg) && pkg.isExtractNativeLibs();
+ boolean extractLibs = !AndroidPackageUtils.isLibrary(pkg)
+ && pkg.isExtractNativeLibrariesRequested();
// We shouldn't attempt to extract libs from system app when it was not updated.
if (isSystemApp && !isUpdatedSystemApp) {
extractLibs = false;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index f708fbb..f338137 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -189,7 +189,7 @@
}
// We do not dexopt a package with no code.
- if (!pkg.isHasCode()) {
+ if (!pkg.isDeclaredHavingCode()) {
return false;
}
@@ -287,7 +287,7 @@
// For each code path in the package, this array contains the class loader context that
// needs to be passed to dexopt in order to ensure correct optimizations.
boolean[] pathsWithCode = new boolean[paths.size()];
- pathsWithCode[0] = pkg.isHasCode();
+ pathsWithCode[0] = pkg.isDeclaredHavingCode();
for (int i = 1; i < paths.size(); i++) {
pathsWithCode[i] = (pkg.getSplitFlags()[i - 1] & ApplicationInfo.FLAG_HAS_CODE) != 0;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ed9d370..0897289 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -804,7 +804,7 @@
+ " PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS flag");
}
- var permissionStates = params.getFinalPermissionStates();
+ var permissionStates = params.getPermissionStates();
if (!permissionStates.isEmpty()) {
if (!hasInstallGrantRuntimePermissions) {
for (int index = 0; index < permissionStates.size(); index++) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3ad3af3e..3951903 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -4878,7 +4878,16 @@
private static void writePermissionsLocked(@NonNull TypedXmlSerializer out,
@NonNull SessionParams params) throws IOException {
- params.writePermissionStateXml(out, TAG_GRANT_PERMISSION, TAG_DENY_PERMISSION, ATTR_NAME);
+ var permissionStates = params.getPermissionStates();
+ for (int index = 0; index < permissionStates.size(); index++) {
+ var permissionName = permissionStates.keyAt(index);
+ var state = permissionStates.valueAt(index);
+ String tag = state == SessionParams.PERMISSION_STATE_GRANTED ? TAG_GRANT_PERMISSION
+ : TAG_DENY_PERMISSION;
+ out.startTag(null, tag);
+ writeStringAttribute(out, ATTR_NAME, permissionName);
+ out.endTag(null, tag);
+ }
}
private static void writeWhitelistedRestrictedPermissionsLocked(@NonNull TypedXmlSerializer out,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 382be45..1c24cec 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6679,6 +6679,13 @@
/** @deprecated For legacy shell command only. */
@Override
@Deprecated
+ public void legacyForceDexOpt(String packageName) throws LegacyDexoptDisabledException {
+ mDexOptHelper.forceDexOpt(snapshotComputer(), packageName);
+ }
+
+ /** @deprecated For legacy shell command only. */
+ @Override
+ @Deprecated
public void legacyReconcileSecondaryDexFiles(String packageName)
throws LegacyDexoptDisabledException {
final Computer snapshot = snapshotComputer();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d322fa2..74bd0092a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2002,8 +2002,8 @@
return 0;
}
- public int runForceDexOpt() throws RemoteException {
- mInterface.forceDexOpt(getNextArgRequired());
+ public int runForceDexOpt() throws RemoteException, LegacyDexoptDisabledException {
+ mPm.legacyForceDexOpt(getNextArgRequired());
return 0;
}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 2538871..e4f3e2b 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -547,7 +547,7 @@
*/
public static void assertCodePolicy(AndroidPackage pkg)
throws PackageManagerException {
- final boolean shouldHaveCode = pkg.isHasCode();
+ final boolean shouldHaveCode = pkg.isDeclaredHavingCode();
if (shouldHaveCode && !apkHasCode(pkg.getBaseApkPath())) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Package " + pkg.getBaseApkPath() + " code is missing");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6541b40..f1998f7 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4864,7 +4864,7 @@
pw.println();
if (pkg != null) {
pw.print(prefix); pw.print(" versionName="); pw.println(pkg.getVersionName());
- pw.print(prefix); pw.print(" usesNonSdkApi="); pw.println(pkg.isUsesNonSdkApi());
+ pw.print(prefix); pw.print(" usesNonSdkApi="); pw.println(pkg.isNonSdkApiRequested());
pw.print(prefix); pw.print(" splits="); dumpSplitNames(pw, pkg); pw.println();
final int apkSigningVersion = pkg.getSigningDetails().getSignatureSchemeVersion();
pw.print(prefix); pw.print(" apkSigningVersion="); pw.println(apkSigningVersion);
@@ -4897,25 +4897,25 @@
pw.print(prefix); pw.print(" dataDir="); pw.println(dataDir.getAbsolutePath());
pw.print(prefix); pw.print(" supportsScreens=[");
boolean first = true;
- if (pkg.isSupportsSmallScreens()) {
+ if (pkg.isSmallScreensSupported()) {
if (!first)
pw.print(", ");
first = false;
pw.print("small");
}
- if (pkg.isSupportsNormalScreens()) {
+ if (pkg.isNormalScreensSupported()) {
if (!first)
pw.print(", ");
first = false;
pw.print("medium");
}
- if (pkg.isSupportsLargeScreens()) {
+ if (pkg.isLargeScreensSupported()) {
if (!first)
pw.print(", ");
first = false;
pw.print("large");
}
- if (pkg.isSupportsExtraLargeScreens()) {
+ if (pkg.isExtraLargeScreensSupported()) {
if (!first)
pw.print(", ");
first = false;
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 3cbaebe..36efc0d 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -238,8 +238,9 @@
* the user is created (as it will be passed back to it through
* {@link UserLifecycleListener#onUserCreated(UserInfo, Object)});
*/
- public abstract UserInfo createUserEvenWhenDisallowed(String name, String userType,
- int flags, String[] disallowedPackages, @Nullable Object token)
+ public abstract @NonNull UserInfo createUserEvenWhenDisallowed(
+ @Nullable String name, @NonNull String userType, @UserInfo.UserInfoFlag int flags,
+ @Nullable String[] disallowedPackages, @Nullable Object token)
throws UserManager.CheckedUserOperationException;
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 372b580..317c111 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1362,15 +1362,14 @@
}
try {
if (enableQuietMode) {
- ActivityManager.getService().stopUser(userId, /* force */true, null);
+ ActivityManager.getService().stopUser(userId, /* force= */ true, null);
LocalServices.getService(ActivityManagerInternal.class)
.killForegroundAppsForUser(userId);
} else {
IProgressListener callback = target != null
? new DisableQuietModeUserUnlockedCallback(target)
: null;
- ActivityManager.getService().startUserInBackgroundWithListener(
- userId, callback);
+ ActivityManager.getService().startProfileWithListener(userId, callback);
}
logQuietModeEnabled(userId, enableQuietMode, callingPackage);
} catch (RemoteException e) {
@@ -4495,9 +4494,11 @@
* as well as for {@link UserManager#USER_TYPE_FULL_RESTRICTED}.
*/
@Override
- public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType,
- @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
+ public @NonNull UserInfo createProfileForUserWithThrow(
+ @Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+ @UserIdInt int userId, @Nullable String[] disallowedPackages)
throws ServiceSpecificException {
+
checkCreateUsersPermission(flags);
try {
return createUserInternal(name, userType, flags, userId, disallowedPackages);
@@ -4510,10 +4511,11 @@
* @see #createProfileForUser
*/
@Override
- public UserInfo createProfileForUserEvenWhenDisallowedWithThrow(String name,
- @NonNull String userType,
- @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
+ public @NonNull UserInfo createProfileForUserEvenWhenDisallowedWithThrow(
+ @Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+ @UserIdInt int userId, @Nullable String[] disallowedPackages)
throws ServiceSpecificException {
+
checkCreateUsersPermission(flags);
try {
return createUserInternalUnchecked(name, userType, flags, userId,
@@ -4524,9 +4526,10 @@
}
@Override
- public UserInfo createUserWithThrow(String name, @NonNull String userType,
- @UserInfoFlag int flags)
+ public @NonNull UserInfo createUserWithThrow(
+ @Nullable String name, @NonNull String userType, @UserInfoFlag int flags)
throws ServiceSpecificException {
+
checkCreateUsersPermission(flags);
try {
return createUserInternal(name, userType, flags, UserHandle.USER_NULL,
@@ -4537,7 +4540,10 @@
}
@Override
- public UserInfo preCreateUserWithThrow(String userType) throws ServiceSpecificException {
+ public @NonNull UserInfo preCreateUserWithThrow(
+ @NonNull String userType)
+ throws ServiceSpecificException {
+
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0;
@@ -4557,10 +4563,12 @@
}
@Override
- public UserHandle createUserWithAttributes(
- String userName, String userType, @UserInfoFlag int flags,
- Bitmap userIcon,
- String accountName, String accountType, PersistableBundle accountOptions) {
+ public @NonNull UserHandle createUserWithAttributes(
+ @Nullable String userName, @NonNull String userType, @UserInfoFlag int flags,
+ @Nullable Bitmap userIcon, @Nullable String accountName, @Nullable String accountType,
+ @Nullable PersistableBundle accountOptions)
+ throws ServiceSpecificException {
+
checkCreateUsersPermission(flags);
if (someUserHasAccountNoChecks(accountName, accountType)) {
@@ -4570,12 +4578,7 @@
UserInfo userInfo;
try {
- userInfo = createUserInternal(userName, userType, flags,
- UserHandle.USER_NULL, null);
-
- if (userInfo == null) {
- throw new ServiceSpecificException(USER_OPERATION_ERROR_UNKNOWN);
- }
+ userInfo = createUserInternal(userName, userType, flags, UserHandle.USER_NULL, null);
} catch (UserManager.CheckedUserOperationException e) {
throw e.toServiceSpecificException();
}
@@ -4589,7 +4592,8 @@
return userInfo.getUserHandle();
}
- private UserInfo createUserInternal(@Nullable String name, @NonNull String userType,
+ private @NonNull UserInfo createUserInternal(
+ @Nullable String name, @NonNull String userType,
@UserInfoFlag int flags, @UserIdInt int parentId,
@Nullable String[] disallowedPackages)
throws UserManager.CheckedUserOperationException {
@@ -4611,11 +4615,12 @@
/* preCreate= */ false, disallowedPackages, /* token= */ null);
}
- private UserInfo createUserInternalUnchecked(@Nullable String name,
- @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
- boolean preCreate, @Nullable String[] disallowedPackages,
+ private @NonNull UserInfo createUserInternalUnchecked(
+ @Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+ @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
@Nullable Object token)
throws UserManager.CheckedUserOperationException {
+
final int noneUserId = -1;
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("createUser-" + flags);
@@ -4633,27 +4638,31 @@
}
}
- private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name,
- @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
- boolean preCreate, @Nullable String[] disallowedPackages,
+ private @NonNull UserInfo createUserInternalUncheckedNoTracing(
+ @Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+ @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t, @Nullable Object token)
- throws UserManager.CheckedUserOperationException {
+ throws UserManager.CheckedUserOperationException {
+
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
- Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
- return null;
+ throwCheckedUserOperationException(
+ "Cannot create user of invalid user type: " + userType,
+ USER_OPERATION_ERROR_UNKNOWN);
}
userType = userType.intern(); // Now that we know it's valid, we can intern it.
flags |= userTypeDetails.getDefaultUserInfoFlags();
if (!checkUserTypeConsistency(flags)) {
- Slog.e(LOG_TAG, "Cannot add user. Flags (" + Integer.toHexString(flags)
- + ") and userTypeDetails (" + userType + ") are inconsistent.");
- return null;
+ throwCheckedUserOperationException(
+ "Cannot add user. Flags (" + Integer.toHexString(flags)
+ + ") and userTypeDetails (" + userType + ") are inconsistent.",
+ USER_OPERATION_ERROR_UNKNOWN);
}
if ((flags & UserInfo.FLAG_SYSTEM) != 0) {
- Slog.e(LOG_TAG, "Cannot add user. Flags (" + Integer.toHexString(flags)
- + ") indicated SYSTEM user, which cannot be created.");
- return null;
+ throwCheckedUserOperationException(
+ "Cannot add user. Flags (" + Integer.toHexString(flags)
+ + ") indicated SYSTEM user, which cannot be created.",
+ USER_OPERATION_ERROR_UNKNOWN);
}
if (!isUserTypeEnabled(userTypeDetails)) {
throwCheckedUserOperationException(
@@ -4679,7 +4688,8 @@
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm.isMemoryLow()) {
- throwCheckedUserOperationException("Cannot add user. Not enough space on disk.",
+ throwCheckedUserOperationException(
+ "Cannot add user. Not enough space on disk.",
UserManager.USER_OPERATION_ERROR_LOW_STORAGE);
}
@@ -4707,7 +4717,8 @@
}
}
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
- throwCheckedUserOperationException("Cannot add more users of type " + userType
+ throwCheckedUserOperationException(
+ "Cannot add more users of type " + userType
+ ". Maximum number of that type already exists.",
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
@@ -5332,13 +5343,13 @@
* @hide
*/
@Override
- public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) {
+ public @NonNull UserInfo createRestrictedProfileWithThrow(
+ @Nullable String name, @UserIdInt int parentUserId)
+ throws ServiceSpecificException {
+
checkCreateUsersPermission("setupRestrictedProfile");
final UserInfo user = createProfileForUserWithThrow(
name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null);
- if (user == null) {
- return null;
- }
final long identity = Binder.clearCallingIdentity();
try {
setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user.id);
@@ -6339,8 +6350,10 @@
setSeedAccountDataNoChecks(userId, accountName, accountType, accountOptions, persist);
}
- private void setSeedAccountDataNoChecks(@UserIdInt int userId, String accountName,
- String accountType, PersistableBundle accountOptions, boolean persist) {
+ private void setSeedAccountDataNoChecks(@UserIdInt int userId, @Nullable String accountName,
+ @Nullable String accountType, @Nullable PersistableBundle accountOptions,
+ boolean persist) {
+
synchronized (mPackagesLock) {
final UserData userData;
synchronized (mUsersLock) {
@@ -6909,9 +6922,11 @@
}
@Override
- public UserInfo createUserEvenWhenDisallowed(String name, @NonNull String userType,
- @UserInfoFlag int flags, String[] disallowedPackages, @Nullable Object token)
+ public @NonNull UserInfo createUserEvenWhenDisallowed(
+ @Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+ @Nullable String[] disallowedPackages, @Nullable Object token)
throws UserManager.CheckedUserOperationException {
+
return createUserInternalUnchecked(name, userType, flags,
UserHandle.USER_NULL, /* preCreated= */ false, disallowedPackages, token);
}
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index d8b6cd5..d88b66b 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -580,7 +580,7 @@
*/
private ArrayMap<String, String> getPackageProfileNames(AndroidPackage pkg) {
ArrayMap<String, String> result = new ArrayMap<>();
- if (pkg.isHasCode()) {
+ if (pkg.isDeclaredHavingCode()) {
result.put(pkg.getBaseApkPath(), ArtManager.getProfileName(null));
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a3be8d3..d108e14 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -133,7 +133,7 @@
info.splitRevisionCodes = pkg.getSplitRevisionCodes();
info.versionName = pkg.getVersionName();
info.sharedUserId = pkg.getSharedUserId();
- info.sharedUserLabel = pkg.getSharedUserLabelRes();
+ info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
info.applicationInfo = applicationInfo;
info.installLocation = pkg.getInstallLocation();
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
@@ -876,27 +876,27 @@
// @formatter:off
int pkgWithoutStateFlags = flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
| flag(pkg.isHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
- | flag(pkg.isAllowBackup(), ApplicationInfo.FLAG_ALLOW_BACKUP)
- | flag(pkg.isKillAfterRestore(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
+ | flag(pkg.isBackupAllowed(), ApplicationInfo.FLAG_ALLOW_BACKUP)
+ | flag(pkg.isKillAfterRestoreAllowed(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
| flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
| flag(pkg.isFullBackupOnly(), ApplicationInfo.FLAG_FULL_BACKUP_ONLY)
| flag(pkg.isPersistent(), ApplicationInfo.FLAG_PERSISTENT)
| flag(pkg.isDebuggable(), ApplicationInfo.FLAG_DEBUGGABLE)
| flag(pkg.isVmSafeMode(), ApplicationInfo.FLAG_VM_SAFE_MODE)
- | flag(pkg.isHasCode(), ApplicationInfo.FLAG_HAS_CODE)
- | flag(pkg.isAllowTaskReparenting(), ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING)
- | flag(pkg.isAllowClearUserData(), ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)
+ | flag(pkg.isDeclaredHavingCode(), ApplicationInfo.FLAG_HAS_CODE)
+ | flag(pkg.isTaskReparentingAllowed(), ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING)
+ | flag(pkg.isClearUserDataAllowed(), ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)
| flag(pkg.isLargeHeap(), ApplicationInfo.FLAG_LARGE_HEAP)
- | flag(pkg.isUsesCleartextTraffic(), ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC)
- | flag(pkg.isSupportsRtl(), ApplicationInfo.FLAG_SUPPORTS_RTL)
+ | flag(pkg.isCleartextTrafficAllowed(), ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC)
+ | flag(pkg.isRtlSupported(), ApplicationInfo.FLAG_SUPPORTS_RTL)
| flag(pkg.isTestOnly(), ApplicationInfo.FLAG_TEST_ONLY)
| flag(pkg.isMultiArch(), ApplicationInfo.FLAG_MULTIARCH)
- | flag(pkg.isExtractNativeLibs(), ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS)
+ | flag(pkg.isExtractNativeLibrariesRequested(), ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS)
| flag(pkg.isGame(), ApplicationInfo.FLAG_IS_GAME)
- | flag(pkg.isSupportsSmallScreens(), ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS)
- | flag(pkg.isSupportsNormalScreens(), ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS)
- | flag(pkg.isSupportsLargeScreens(), ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS)
- | flag(pkg.isSupportsExtraLargeScreens(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS)
+ | flag(pkg.isSmallScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS)
+ | flag(pkg.isNormalScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS)
+ | flag(pkg.isLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS)
+ | flag(pkg.isExtraLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS)
| flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS)
| flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
| flag(AndroidPackageUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM)
@@ -932,12 +932,12 @@
| flag(pkg.isDefaultToDeviceProtectedStorage(), ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE)
| flag(pkg.isDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE)
| flag(pkg.isPartiallyDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE)
- | flag(pkg.isAllowClearUserDataOnFailedRestore(), ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE)
+ | flag(pkg.isClearUserDataOnFailedRestoreAllowed(), ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE)
| flag(pkg.isAllowAudioPlaybackCapture(), ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE)
| flag(pkg.isRequestLegacyExternalStorage(), ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE)
- | flag(pkg.isUsesNonSdkApi(), ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API)
- | flag(pkg.isHasFragileUserData(), ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
- | flag(pkg.isCantSaveState(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
+ | flag(pkg.isNonSdkApiRequested(), ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API)
+ | flag(pkg.isUserDataFragile(), ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
+ | flag(pkg.isSaveStateDisallowed(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
| flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION)
| flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING)
| flag(AndroidPackageUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index f3ee531..e2acc17 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -59,7 +59,7 @@
AndroidPackage aPkg) {
PackageImpl pkg = (PackageImpl) aPkg;
ArrayList<String> paths = new ArrayList<>();
- if (pkg.isHasCode()) {
+ if (pkg.isDeclaredHavingCode()) {
paths.add(pkg.getBaseApkPath());
}
String[] splitCodePaths = pkg.getSplitCodePaths();
@@ -156,7 +156,7 @@
return NativeLibraryHelper.Handle.create(
AndroidPackageUtils.getAllCodePaths(pkg),
pkg.isMultiArch(),
- pkg.isExtractNativeLibs(),
+ pkg.isExtractNativeLibrariesRequested(),
pkg.isDebuggable()
);
}
@@ -243,7 +243,7 @@
} else if (pkg.isSignedWithPlatformKey()) {
isAllowedToUseHiddenApis = true;
} else if (packageState.isSystem()) {
- isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
+ isAllowedToUseHiddenApis = pkg.isNonSdkApiRequested()
|| SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(
pkg.getPackageName());
} else {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index d7c4a09..de31b46 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -793,7 +793,7 @@
null,
getBaseApkPath(),
getBaseRevisionCode(),
- isHasCode() ? ApplicationInfo.FLAG_HAS_CODE : 0,
+ isDeclaredHavingCode() ? ApplicationInfo.FLAG_HAS_CODE : 0,
getClassLoaderName()
));
@@ -879,7 +879,7 @@
}
@Override
- public int getBannerRes() {
+ public int getBannerResourceId() {
return banner;
}
@@ -934,12 +934,12 @@
}
@Override
- public int getDataExtractionRulesRes() {
+ public int getDataExtractionRulesResourceId() {
return dataExtractionRules;
}
@Override
- public int getDescriptionRes() {
+ public int getDescriptionResourceId() {
return descriptionRes;
}
@@ -950,7 +950,7 @@
}
@Override
- public int getFullBackupContentRes() {
+ public int getFullBackupContentResourceId() {
return fullBackupContent;
}
@@ -961,7 +961,7 @@
}
@Override
- public int getIconRes() {
+ public int getIconResourceId() {
return iconRes;
}
@@ -995,7 +995,7 @@
}
@Override
- public int getLabelRes() {
+ public int getLabelResourceId() {
return labelRes;
}
@@ -1011,12 +1011,12 @@
}
@Override
- public int getLocaleConfigRes() {
+ public int getLocaleConfigResourceId() {
return mLocaleConfigRes;
}
@Override
- public int getLogoRes() {
+ public int getLogoResourceId() {
return logo;
}
@@ -1077,7 +1077,7 @@
}
@Override
- public int getNetworkSecurityConfigRes() {
+ public int getNetworkSecurityConfigResourceId() {
return networkSecurityConfigRes;
}
@@ -1259,7 +1259,7 @@
}
@Override
- public int getRoundIconRes() {
+ public int getRoundIconResourceId() {
return roundIconRes;
}
@@ -1287,7 +1287,7 @@
}
@Override
- public int getSharedUserLabelRes() {
+ public int getSharedUserLabelResourceId() {
return sharedUserLabel;
}
@@ -1366,7 +1366,7 @@
}
@Override
- public int getThemeRes() {
+ public int getThemeResourceId() {
return theme;
}
@@ -1531,17 +1531,17 @@
}
@Override
- public boolean isAllowBackup() {
+ public boolean isBackupAllowed() {
return getBoolean(Booleans.ALLOW_BACKUP);
}
@Override
- public boolean isAllowClearUserData() {
+ public boolean isClearUserDataAllowed() {
return getBoolean(Booleans.ALLOW_CLEAR_USER_DATA);
}
@Override
- public boolean isAllowClearUserDataOnFailedRestore() {
+ public boolean isClearUserDataOnFailedRestoreAllowed() {
return getBoolean(Booleans.ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE);
}
@@ -1551,7 +1551,7 @@
}
@Override
- public boolean isAllowTaskReparenting() {
+ public boolean isTaskReparentingAllowed() {
return getBoolean(Booleans.ALLOW_TASK_REPARENTING);
}
@@ -1574,7 +1574,7 @@
}
@Override
- public boolean isCantSaveState() {
+ public boolean isSaveStateDisallowed() {
return getBoolean(Booleans.CANT_SAVE_STATE);
}
@@ -1609,7 +1609,7 @@
}
@Override
- public boolean isExtractNativeLibs() {
+ public boolean isExtractNativeLibrariesRequested() {
return getBoolean(Booleans.EXTRACT_NATIVE_LIBS);
}
@@ -1629,7 +1629,7 @@
}
@Override
- public boolean isHasCode() {
+ public boolean isDeclaredHavingCode() {
return getBoolean(Booleans.HAS_CODE);
}
@@ -1639,7 +1639,7 @@
}
@Override
- public boolean isHasFragileUserData() {
+ public boolean isUserDataFragile() {
return getBoolean(Booleans.HAS_FRAGILE_USER_DATA);
}
@@ -1649,7 +1649,7 @@
}
@Override
- public boolean isKillAfterRestore() {
+ public boolean isKillAfterRestoreAllowed() {
return getBoolean(Booleans.KILL_AFTER_RESTORE);
}
@@ -1746,7 +1746,7 @@
return getBoolean(Booleans.STATIC_SHARED_LIBRARY);
}
- public boolean isSupportsExtraLargeScreens() {
+ public boolean isExtraLargeScreensSupported() {
if (supportsExtraLargeScreens == null) {
return targetSdkVersion >= Build.VERSION_CODES.GINGERBREAD;
}
@@ -1754,7 +1754,7 @@
return supportsExtraLargeScreens;
}
- public boolean isSupportsLargeScreens() {
+ public boolean isLargeScreensSupported() {
if (supportsLargeScreens == null) {
return targetSdkVersion >= Build.VERSION_CODES.DONUT;
}
@@ -1762,16 +1762,16 @@
return supportsLargeScreens;
}
- public boolean isSupportsNormalScreens() {
+ public boolean isNormalScreensSupported() {
return supportsNormalScreens == null || supportsNormalScreens;
}
@Override
- public boolean isSupportsRtl() {
+ public boolean isRtlSupported() {
return getBoolean(Booleans.SUPPORTS_RTL);
}
- public boolean isSupportsSmallScreens() {
+ public boolean isSmallScreensSupported() {
if (supportsSmallScreens == null) {
return targetSdkVersion >= Build.VERSION_CODES.DONUT;
}
@@ -1785,7 +1785,7 @@
}
@Override
- public boolean isUse32BitAbi() {
+ public boolean is32BitAbiPreferred() {
return getBoolean(Booleans.USE_32_BIT_ABI);
}
@@ -1795,12 +1795,12 @@
}
@Override
- public boolean isUsesCleartextTraffic() {
+ public boolean isCleartextTrafficAllowed() {
return getBoolean(Booleans.USES_CLEARTEXT_TRAFFIC);
}
@Override
- public boolean isUsesNonSdkApi() {
+ public boolean isNonSdkApiRequested() {
return getBoolean(Booleans.USES_NON_SDK_API);
}
@@ -1831,17 +1831,17 @@
}
@Override
- public PackageImpl setAllowBackup(boolean value) {
+ public PackageImpl setBackupAllowed(boolean value) {
return setBoolean(Booleans.ALLOW_BACKUP, value);
}
@Override
- public PackageImpl setAllowClearUserData(boolean value) {
+ public PackageImpl setClearUserDataAllowed(boolean value) {
return setBoolean(Booleans.ALLOW_CLEAR_USER_DATA, value);
}
@Override
- public PackageImpl setAllowClearUserDataOnFailedRestore(boolean value) {
+ public PackageImpl setClearUserDataOnFailedRestoreAllowed(boolean value) {
return setBoolean(Booleans.ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE, value);
}
@@ -1851,7 +1851,7 @@
}
@Override
- public PackageImpl setAllowTaskReparenting(boolean value) {
+ public PackageImpl setTaskReparentingAllowed(boolean value) {
return setBoolean(Booleans.ALLOW_TASK_REPARENTING, value);
}
@@ -1895,7 +1895,7 @@
}
@Override
- public PackageImpl setBannerRes(int value) {
+ public PackageImpl setBannerResourceId(int value) {
banner = value;
return this;
}
@@ -1912,7 +1912,7 @@
}
@Override
- public PackageImpl setCantSaveState(boolean value) {
+ public PackageImpl setSaveStateDisallowed(boolean value) {
return setBoolean(Booleans.CANT_SAVE_STATE, value);
}
@@ -1958,7 +1958,7 @@
}
@Override
- public PackageImpl setDataExtractionRulesRes(int value) {
+ public PackageImpl setDataExtractionRulesResourceId(int value) {
dataExtractionRules = value;
return this;
}
@@ -1969,7 +1969,7 @@
}
@Override
- public PackageImpl setDescriptionRes(int value) {
+ public PackageImpl setDescriptionResourceId(int value) {
descriptionRes = value;
return this;
}
@@ -1985,7 +1985,7 @@
}
@Override
- public PackageImpl setExtractNativeLibs(boolean value) {
+ public PackageImpl setExtractNativeLibrariesRequested(boolean value) {
return setBoolean(Booleans.EXTRACT_NATIVE_LIBS, value);
}
@@ -1995,7 +1995,7 @@
}
@Override
- public PackageImpl setFullBackupContentRes(int value) {
+ public PackageImpl setFullBackupContentResourceId(int value) {
fullBackupContent = value;
return this;
}
@@ -2017,7 +2017,7 @@
}
@Override
- public PackageImpl setHasCode(boolean value) {
+ public PackageImpl setDeclaredHavingCode(boolean value) {
return setBoolean(Booleans.HAS_CODE, value);
}
@@ -2027,12 +2027,12 @@
}
@Override
- public PackageImpl setHasFragileUserData(boolean value) {
+ public PackageImpl setUserDataFragile(boolean value) {
return setBoolean(Booleans.HAS_FRAGILE_USER_DATA, value);
}
@Override
- public PackageImpl setIconRes(int value) {
+ public PackageImpl setIconResourceId(int value) {
iconRes = value;
return this;
}
@@ -2049,7 +2049,7 @@
}
@Override
- public PackageImpl setKillAfterRestore(boolean value) {
+ public PackageImpl setKillAfterRestoreAllowed(boolean value) {
return setBoolean(Booleans.KILL_AFTER_RESTORE, value);
}
@@ -2060,7 +2060,7 @@
}
@Override
- public PackageImpl setLabelRes(int value) {
+ public PackageImpl setLabelResourceId(int value) {
labelRes = value;
return this;
}
@@ -2082,13 +2082,13 @@
}
@Override
- public PackageImpl setLocaleConfigRes(int value) {
+ public PackageImpl setLocaleConfigResourceId(int value) {
mLocaleConfigRes = value;
return this;
}
@Override
- public PackageImpl setLogoRes(int value) {
+ public PackageImpl setLogoResourceId(int value) {
logo = value;
return this;
}
@@ -2154,7 +2154,7 @@
}
@Override
- public PackageImpl setNetworkSecurityConfigRes(int value) {
+ public PackageImpl setNetworkSecurityConfigResourceId(int value) {
networkSecurityConfigRes = value;
return this;
}
@@ -2318,7 +2318,7 @@
}
@Override
- public PackageImpl setRoundIconRes(int value) {
+ public PackageImpl setRoundIconResourceId(int value) {
roundIconRes = value;
return this;
}
@@ -2347,7 +2347,7 @@
}
@Override
- public PackageImpl setSharedUserLabelRes(int value) {
+ public PackageImpl setSharedUserLabelResourceId(int value) {
sharedUserLabel = value;
return this;
}
@@ -2384,7 +2384,7 @@
}
@Override
- public PackageImpl setSupportsExtraLargeScreens(int supportsExtraLargeScreens) {
+ public PackageImpl setExtraLargeScreensSupported(int supportsExtraLargeScreens) {
if (supportsExtraLargeScreens == 1) {
return this;
}
@@ -2394,7 +2394,7 @@
}
@Override
- public PackageImpl setSupportsLargeScreens(int supportsLargeScreens) {
+ public PackageImpl setLargeScreensSupported(int supportsLargeScreens) {
if (supportsLargeScreens == 1) {
return this;
}
@@ -2404,7 +2404,7 @@
}
@Override
- public PackageImpl setSupportsNormalScreens(int supportsNormalScreens) {
+ public PackageImpl setNormalScreensSupported(int supportsNormalScreens) {
if (supportsNormalScreens == 1) {
return this;
}
@@ -2414,12 +2414,12 @@
}
@Override
- public PackageImpl setSupportsRtl(boolean value) {
+ public PackageImpl setRtlSupported(boolean value) {
return setBoolean(Booleans.SUPPORTS_RTL, value);
}
@Override
- public PackageImpl setSupportsSmallScreens(int supportsSmallScreens) {
+ public PackageImpl setSmallScreensSupported(int supportsSmallScreens) {
if (supportsSmallScreens == 1) {
return this;
}
@@ -2452,7 +2452,7 @@
}
@Override
- public PackageImpl setThemeRes(int value) {
+ public PackageImpl setThemeResourceId(int value) {
theme = value;
return this;
}
@@ -2470,7 +2470,7 @@
}
@Override
- public PackageImpl setUse32BitAbi(boolean value) {
+ public PackageImpl set32BitAbiPreferred(boolean value) {
return setBoolean(Booleans.USE_32_BIT_ABI, value);
}
@@ -2480,12 +2480,12 @@
}
@Override
- public PackageImpl setUsesCleartextTraffic(boolean value) {
+ public PackageImpl setCleartextTrafficAllowed(boolean value) {
return setBoolean(Booleans.USES_CLEARTEXT_TRAFFIC, value);
}
@Override
- public PackageImpl setUsesNonSdkApi(boolean value) {
+ public PackageImpl setNonSdkApiRequested(boolean value) {
return setBoolean(Booleans.USES_NON_SDK_API, value);
}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index ad73873..2fdda12 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -116,7 +116,7 @@
* @see R.styleable#AndroidManifestApplication_banner
*/
@DrawableRes
- int getBannerRes();
+ int getBannerResourceId();
/**
* @see PackageInfo#baseRevisionCode
@@ -149,21 +149,21 @@
* @see R.styleable#AndroidManifestApplication_dataExtractionRules
*/
@XmlRes
- int getDataExtractionRulesRes();
+ int getDataExtractionRulesResourceId();
/**
* @see ApplicationInfo#descriptionRes
* @see R.styleable#AndroidManifestApplication_description
*/
@StringRes // This is actually format="reference"
- int getDescriptionRes();
+ int getDescriptionResourceId();
/**
* @see ApplicationInfo#fullBackupContent
* @see R.styleable#AndroidManifestApplication_fullBackupContent
*/
@XmlRes
- int getFullBackupContentRes();
+ int getFullBackupContentResourceId();
/**
* @see ApplicationInfo#getGwpAsanMode()
@@ -177,14 +177,14 @@
* @see R.styleable#AndroidManifestApplication_icon
*/
@DrawableRes
- int getIconRes();
+ int getIconResourceId();
/**
* @see ApplicationInfo#labelRes
* @see R.styleable#AndroidManifestApplication_label
*/
@StringRes
- int getLabelRes();
+ int getLabelResourceId();
/**
* @see ApplicationInfo#largestWidthLimitDp
@@ -206,7 +206,7 @@
* @see R.styleable#AndroidManifestApplication_logo
*/
@DrawableRes
- int getLogoRes();
+ int getLogoResourceId();
/**
* The resource ID used to provide the application's locales configuration.
@@ -214,7 +214,7 @@
* @see R.styleable#AndroidManifestApplication_localeConfig
*/
@XmlRes
- int getLocaleConfigRes();
+ int getLocaleConfigResourceId();
/**
* @see PackageInfo#getLongVersionCode()
@@ -247,7 +247,7 @@
* @see R.styleable#AndroidManifestApplication_networkSecurityConfig
*/
@XmlRes
- int getNetworkSecurityConfigRes();
+ int getNetworkSecurityConfigResourceId();
/**
* @see PackageInfo#requiredAccountType
@@ -277,7 +277,7 @@
* @see R.styleable#AndroidManifestApplication_roundIcon
*/
@DrawableRes
- int getRoundIconRes();
+ int getRoundIconResourceId();
/**
* @see R.styleable#AndroidManifestSdkLibrary_name
@@ -297,7 +297,7 @@
* @see R.styleable#AndroidManifest_sharedUserLabel
*/
@StringRes
- int getSharedUserLabelRes();
+ int getSharedUserLabelResourceId();
/**
* @return List of all splits for a package. Note that base.apk is considered a
@@ -336,7 +336,7 @@
* @see R.styleable#AndroidManifestApplication_theme
*/
@StyleRes
- int getThemeRes();
+ int getThemeResourceId();
/**
* @see ApplicationInfo#uiOptions
@@ -367,19 +367,19 @@
* @see ApplicationInfo#FLAG_ALLOW_BACKUP
* @see R.styleable#AndroidManifestApplication_allowBackup
*/
- boolean isAllowBackup();
+ boolean isBackupAllowed();
/**
* @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
* @see R.styleable#AndroidManifestApplication_allowClearUserData
*/
- boolean isAllowClearUserData();
+ boolean isClearUserDataAllowed();
/**
* @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
* @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
*/
- boolean isAllowClearUserDataOnFailedRestore();
+ boolean isClearUserDataOnFailedRestoreAllowed();
/**
* @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
@@ -391,7 +391,7 @@
* @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
* @see R.styleable#AndroidManifestApplication_allowTaskReparenting
*/
- boolean isAllowTaskReparenting();
+ boolean isTaskReparentingAllowed();
/**
* If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
@@ -424,7 +424,7 @@
* @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
* @see R.styleable#AndroidManifestApplication_cantSaveState
*/
- boolean isCantSaveState();
+ boolean isSaveStateDisallowed();
/**
* @see PackageInfo#coreApp
@@ -459,7 +459,7 @@
* @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
* @see R.styleable#AndroidManifestApplication_extractNativeLibs
*/
- boolean isExtractNativeLibs();
+ boolean isExtractNativeLibrariesRequested();
/**
* @see ApplicationInfo#FLAG_FACTORY_TEST
@@ -481,13 +481,13 @@
* @see ApplicationInfo#FLAG_HAS_CODE
* @see R.styleable#AndroidManifestApplication_hasCode
*/
- boolean isHasCode();
+ boolean isDeclaredHavingCode();
/**
* @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
* @see R.styleable#AndroidManifestApplication_hasFragileUserData
*/
- boolean isHasFragileUserData();
+ boolean isUserDataFragile();
/**
* @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
@@ -499,7 +499,7 @@
* @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
* @see R.styleable#AndroidManifestApplication_killAfterRestore
*/
- boolean isKillAfterRestore();
+ boolean isKillAfterRestoreAllowed();
/**
* @see ApplicationInfo#FLAG_LARGE_HEAP
@@ -596,7 +596,7 @@
* @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
* @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
*/
- boolean isSupportsExtraLargeScreens();
+ boolean isExtraLargeScreensSupported();
/**
* If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
@@ -605,7 +605,7 @@
* @see R.styleable#AndroidManifestSupportsScreens_largeScreens
* @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
*/
- boolean isSupportsLargeScreens();
+ boolean isLargeScreensSupported();
/**
* If omitted from manifest, returns true.
@@ -613,13 +613,13 @@
* @see R.styleable#AndroidManifestSupportsScreens_normalScreens
* @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
*/
- boolean isSupportsNormalScreens();
+ boolean isNormalScreensSupported();
/**
* @see ApplicationInfo#FLAG_SUPPORTS_RTL
* @see R.styleable#AndroidManifestApplication_supportsRtl
*/
- boolean isSupportsRtl();
+ boolean isRtlSupported();
/**
* If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
@@ -628,7 +628,7 @@
* @see R.styleable#AndroidManifestSupportsScreens_smallScreens
* @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
*/
- boolean isSupportsSmallScreens();
+ boolean isSmallScreensSupported();
/**
* @see ApplicationInfo#FLAG_TEST_ONLY
@@ -643,13 +643,13 @@
*
* @see R.attr#use32bitAbi
*/
- boolean isUse32BitAbi();
+ boolean is32BitAbiPreferred();
/**
* @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
* @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
*/
- boolean isUsesCleartextTraffic();
+ boolean isCleartextTrafficAllowed();
/**
* @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
@@ -661,7 +661,7 @@
* @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API
* @see R.styleable#AndroidManifestApplication_usesNonSdkApi
*/
- boolean isUsesNonSdkApi();
+ boolean isNonSdkApiRequested();
/**
* @see ApplicationInfo#FLAG_VM_SAFE_MODE
@@ -892,7 +892,7 @@
/**
* If {@link R.styleable#AndroidManifestApplication_label} is a string literal, this is it.
- * Otherwise, it's stored as {@link #getLabelRes()}.
+ * Otherwise, it's stored as {@link #getLabelResourceId()}.
*
* @see ApplicationInfo#nonLocalizedLabel
* @see R.styleable#AndroidManifestApplication_label
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 106b149..2c37876 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -160,6 +160,14 @@
/**
* List of shared libraries that this package declares a dependency on. This includes all
* types of libraries, system or app provided and Java or native.
+ * <p/>
+ * This includes libraries declared in the manifest under the following tags:
+ * <ul>
+ * <li>uses-library</li>
+ * <li>uses-native-library</li>
+ * <li>uses-sdk-library</li>
+ * <li>uses-static-library</li>
+ * </ul>
*/
@NonNull
List<SharedLibrary> getSharedLibraryDependencies();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index b73ff34..ee793c8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -114,7 +114,7 @@
return input.error(result);
}
- if (receiver && pkg.isCantSaveState()) {
+ if (receiver && pkg.isSaveStateDisallowed()) {
// A heavy-weight application can not have receivers in its main process
if (Objects.equals(activity.getProcessName(), packageName)) {
return input.error("Heavy-weight applications can not have receivers "
@@ -129,7 +129,7 @@
activity.setTheme(sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0))
.setUiOptions(sa.getInt(R.styleable.AndroidManifestActivity_uiOptions, pkg.getUiOptions()));
- activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_ALLOW_TASK_REPARENTING, R.styleable.AndroidManifestActivity_allowTaskReparenting, pkg.isAllowTaskReparenting(), sa)
+ activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_ALLOW_TASK_REPARENTING, R.styleable.AndroidManifestActivity_allowTaskReparenting, pkg.isTaskReparentingAllowed(), sa)
| flag(ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE, R.styleable.AndroidManifestActivity_alwaysRetainTaskState, sa)
| flag(ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH, R.styleable.AndroidManifestActivity_clearTaskOnLaunch, sa)
| flag(ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS, R.styleable.AndroidManifestActivity_excludeFromRecents, sa)
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
index 4cb4dd0..37bed15 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
@@ -136,7 +136,7 @@
sa.recycle();
}
- if (pkg.isCantSaveState()) {
+ if (pkg.isSaveStateDisallowed()) {
// A heavy-weight application can not have providers in its main process
if (Objects.equals(provider.getProcessName(), packageName)) {
return input.error("Heavy-weight applications can not have providers"
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index a812257..c15266f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -115,7 +115,7 @@
sa.recycle();
}
- if (pkg.isCantSaveState()) {
+ if (pkg.isSaveStateDisallowed()) {
// A heavy-weight application can not have services in its main process
// We can do direct compare because we intern all strings.
if (Objects.equals(service.getProcessName(), packageName)) {
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index bb36758..6cb6a97 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -163,19 +163,20 @@
ParsingPackage setAllowAudioPlaybackCapture(boolean allowAudioPlaybackCapture);
- ParsingPackage setAllowBackup(boolean allowBackup);
+ ParsingPackage setBackupAllowed(boolean allowBackup);
- ParsingPackage setAllowClearUserData(boolean allowClearUserData);
+ ParsingPackage setClearUserDataAllowed(boolean allowClearUserData);
- ParsingPackage setAllowClearUserDataOnFailedRestore(boolean allowClearUserDataOnFailedRestore);
+ ParsingPackage setClearUserDataOnFailedRestoreAllowed(
+ boolean allowClearUserDataOnFailedRestore);
- ParsingPackage setAllowTaskReparenting(boolean allowTaskReparenting);
+ ParsingPackage setTaskReparentingAllowed(boolean allowTaskReparenting);
ParsingPackage setResourceOverlay(boolean isResourceOverlay);
ParsingPackage setBackupInForeground(boolean backupInForeground);
- ParsingPackage setCantSaveState(boolean cantSaveState);
+ ParsingPackage setSaveStateDisallowed(boolean cantSaveState);
ParsingPackage setDebuggable(boolean debuggable);
@@ -185,19 +186,19 @@
ParsingPackage setExternalStorage(boolean externalStorage);
- ParsingPackage setExtractNativeLibs(boolean extractNativeLibs);
+ ParsingPackage setExtractNativeLibrariesRequested(boolean extractNativeLibs);
ParsingPackage setFullBackupOnly(boolean fullBackupOnly);
- ParsingPackage setHasCode(boolean hasCode);
+ ParsingPackage setDeclaredHavingCode(boolean hasCode);
- ParsingPackage setHasFragileUserData(boolean hasFragileUserData);
+ ParsingPackage setUserDataFragile(boolean hasFragileUserData);
ParsingPackage setGame(boolean isGame);
ParsingPackage setIsolatedSplitLoading(boolean isolatedSplitLoading);
- ParsingPackage setKillAfterRestore(boolean killAfterRestore);
+ ParsingPackage setKillAfterRestoreAllowed(boolean killAfterRestore);
ParsingPackage setLargeHeap(boolean largeHeap);
@@ -231,15 +232,15 @@
ParsingPackage setStaticSharedLibrary(boolean staticSharedLibrary);
- ParsingPackage setSupportsRtl(boolean supportsRtl);
+ ParsingPackage setRtlSupported(boolean supportsRtl);
ParsingPackage setTestOnly(boolean testOnly);
ParsingPackage setUseEmbeddedDex(boolean useEmbeddedDex);
- ParsingPackage setUsesCleartextTraffic(boolean usesCleartextTraffic);
+ ParsingPackage setCleartextTrafficAllowed(boolean usesCleartextTraffic);
- ParsingPackage setUsesNonSdkApi(boolean usesNonSdkApi);
+ ParsingPackage setNonSdkApiRequested(boolean usesNonSdkApi);
ParsingPackage setVisibleToInstantApps(boolean visibleToInstantApps);
@@ -255,7 +256,7 @@
ParsingPackage setBackupAgentName(String backupAgentName);
- ParsingPackage setBannerRes(int banner);
+ ParsingPackage setBannerResourceId(int banner);
ParsingPackage setCategory(int category);
@@ -265,7 +266,7 @@
ParsingPackage setCompatibleWidthLimitDp(int compatibleWidthLimitDp);
- ParsingPackage setDescriptionRes(int descriptionRes);
+ ParsingPackage setDescriptionResourceId(int descriptionRes);
ParsingPackage setEnabled(boolean enabled);
@@ -281,24 +282,24 @@
ParsingPackage setCrossProfile(boolean crossProfile);
- ParsingPackage setFullBackupContentRes(int fullBackupContentRes);
+ ParsingPackage setFullBackupContentResourceId(int fullBackupContentRes);
- ParsingPackage setDataExtractionRulesRes(int dataExtractionRulesRes);
+ ParsingPackage setDataExtractionRulesResourceId(int dataExtractionRulesRes);
ParsingPackage setHasDomainUrls(boolean hasDomainUrls);
- ParsingPackage setIconRes(int iconRes);
+ ParsingPackage setIconResourceId(int iconRes);
ParsingPackage setInstallLocation(int installLocation);
/** @see R#styleable.AndroidManifest_sharedUserMaxSdkVersion */
ParsingPackage setLeavingSharedUser(boolean leavingSharedUser);
- ParsingPackage setLabelRes(int labelRes);
+ ParsingPackage setLabelResourceId(int labelRes);
ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
- ParsingPackage setLogoRes(int logo);
+ ParsingPackage setLogoResourceId(int logo);
ParsingPackage setManageSpaceActivityName(String manageSpaceActivityName);
@@ -308,7 +309,7 @@
ParsingPackage setMaxSdkVersion(int maxSdkVersion);
- ParsingPackage setNetworkSecurityConfigRes(int networkSecurityConfigRes);
+ ParsingPackage setNetworkSecurityConfigResourceId(int networkSecurityConfigRes);
ParsingPackage setNonLocalizedLabel(CharSequence nonLocalizedLabel);
@@ -334,9 +335,9 @@
ParsingPackage setRestrictedAccountType(String restrictedAccountType);
- ParsingPackage setRoundIconRes(int roundIconRes);
+ ParsingPackage setRoundIconResourceId(int roundIconRes);
- ParsingPackage setSharedUserLabelRes(int sharedUserLabelRes);
+ ParsingPackage setSharedUserLabelResourceId(int sharedUserLabelRes);
ParsingPackage setSigningDetails(@NonNull SigningDetails signingDetails);
@@ -344,23 +345,23 @@
ParsingPackage setStaticSharedLibraryVersion(long staticSharedLibraryVersion);
- ParsingPackage setSupportsLargeScreens(int supportsLargeScreens);
+ ParsingPackage setLargeScreensSupported(int supportsLargeScreens);
- ParsingPackage setSupportsNormalScreens(int supportsNormalScreens);
+ ParsingPackage setNormalScreensSupported(int supportsNormalScreens);
- ParsingPackage setSupportsSmallScreens(int supportsSmallScreens);
+ ParsingPackage setSmallScreensSupported(int supportsSmallScreens);
- ParsingPackage setSupportsExtraLargeScreens(int supportsExtraLargeScreens);
+ ParsingPackage setExtraLargeScreensSupported(int supportsExtraLargeScreens);
ParsingPackage setTargetSandboxVersion(int targetSandboxVersion);
- ParsingPackage setThemeRes(int theme);
+ ParsingPackage setThemeResourceId(int theme);
ParsingPackage setRequestForegroundServiceExemption(boolean requestForegroundServiceExemption);
ParsingPackage setUpgradeKeySets(@NonNull Set<String> upgradeKeySets);
- ParsingPackage setUse32BitAbi(boolean use32BitAbi);
+ ParsingPackage set32BitAbiPreferred(boolean use32BitAbi);
ParsingPackage setVolumeUuid(@Nullable String volumeUuid);
@@ -385,7 +386,7 @@
ParsingPackage setResetEnabledSettingsOnAppDataCleared(
boolean resetEnabledSettingsOnAppDataCleared);
- ParsingPackage setLocaleConfigRes(int localeConfigRes);
+ ParsingPackage setLocaleConfigResourceId(int localeConfigRes);
ParsingPackage setAllowUpdateOwnership(boolean value);
@@ -508,15 +509,15 @@
@Nullable
String getZygotePreloadName();
- boolean isAllowBackup();
+ boolean isBackupAllowed();
- boolean isAllowTaskReparenting();
+ boolean isTaskReparentingAllowed();
boolean isAnyDensity();
boolean isHardwareAccelerated();
- boolean isCantSaveState();
+ boolean isSaveStateDisallowed();
boolean isProfileable();
@@ -528,11 +529,11 @@
boolean isStaticSharedLibrary();
- boolean isSupportsExtraLargeScreens();
+ boolean isExtraLargeScreensSupported();
- boolean isSupportsLargeScreens();
+ boolean isLargeScreensSupported();
- boolean isSupportsNormalScreens();
+ boolean isNormalScreensSupported();
- boolean isSupportsSmallScreens();
+ boolean isSmallScreensSupported();
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 31f291f..fda44e4 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -430,7 +430,7 @@
}
}
- pkg.setUse32BitAbi(lite.isUse32bitAbi());
+ pkg.set32BitAbiPreferred(lite.isUse32bitAbi());
return input.success(pkg);
} catch (IllegalArgumentException e) {
return input.error(e.getCause() instanceof IOException ? INSTALL_FAILED_INVALID_APK
@@ -466,7 +466,7 @@
}
return input.success(result.getResult()
- .setUse32BitAbi(lite.isUse32bitAbi()));
+ .set32BitAbiPreferred(lite.isUse32bitAbi()));
} catch (IOException e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
@@ -957,10 +957,10 @@
// cannot be windowed / resized. Note that an SDK version of 0 is common for
// pre-Doughnut applications.
if (pkg.getTargetSdkVersion() < DONUT
- || (!pkg.isSupportsSmallScreens()
- && !pkg.isSupportsNormalScreens()
- && !pkg.isSupportsLargeScreens()
- && !pkg.isSupportsExtraLargeScreens()
+ || (!pkg.isSmallScreensSupported()
+ && !pkg.isNormalScreensSupported()
+ && !pkg.isLargeScreensSupported()
+ && !pkg.isExtraLargeScreensSupported()
&& !pkg.isResizeable()
&& !pkg.isAnyDensity())) {
adjustPackageToBeUnresizeableAndUnpipable(pkg);
@@ -1052,7 +1052,8 @@
return input.success(pkg
.setLeavingSharedUser(leaving)
.setSharedUserId(str.intern())
- .setSharedUserLabelRes(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
+ .setSharedUserLabelResourceId(
+ resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
}
private static ParseResult<ParsingPackage> parseKeySets(ParseInput input,
@@ -1885,7 +1886,7 @@
TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
if (labelValue != null) {
- pkg.setLabelRes(labelValue.resourceId);
+ pkg.setLabelResourceId(labelValue.resourceId);
if (labelValue.resourceId == 0) {
pkg.setNonLocalizedLabel(labelValue.coerceToString());
}
@@ -1906,7 +1907,7 @@
pkg.setManageSpaceActivityName(manageSpaceActivityName);
}
- if (pkg.isAllowBackup()) {
+ if (pkg.isBackupAllowed()) {
// backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
// and restoreAnyVersion are only relevant if backup is possible for the
// given application.
@@ -1924,7 +1925,7 @@
}
pkg.setBackupAgentName(backupAgentName)
- .setKillAfterRestore(bool(true,
+ .setKillAfterRestoreAllowed(bool(true,
R.styleable.AndroidManifestApplication_killAfterRestore, sa))
.setRestoreAnyVersion(bool(false,
R.styleable.AndroidManifestApplication_restoreAnyVersion, sa))
@@ -1950,7 +1951,7 @@
fullBackupContent = v.data == 0 ? -1 : 0;
}
- pkg.setFullBackupContentRes(fullBackupContent);
+ pkg.setFullBackupContentResourceId(fullBackupContent);
}
if (DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
@@ -2024,7 +2025,7 @@
String processName = processNameResult.getResult();
pkg.setProcessName(processName);
- if (pkg.isCantSaveState()) {
+ if (pkg.isSaveStateDisallowed()) {
// A heavy-weight application can not be in a custom process.
// We can do direct compare because we intern all strings.
if (processName != null && !processName.equals(pkgName)) {
@@ -2224,31 +2225,31 @@
// CHECKSTYLE:off
pkg
// Default true
- .setAllowBackup(bool(true, R.styleable.AndroidManifestApplication_allowBackup, sa))
- .setAllowClearUserData(bool(true, R.styleable.AndroidManifestApplication_allowClearUserData, sa))
- .setAllowClearUserDataOnFailedRestore(bool(true, R.styleable.AndroidManifestApplication_allowClearUserDataOnFailedRestore, sa))
+ .setBackupAllowed(bool(true, R.styleable.AndroidManifestApplication_allowBackup, sa))
+ .setClearUserDataAllowed(bool(true, R.styleable.AndroidManifestApplication_allowClearUserData, sa))
+ .setClearUserDataOnFailedRestoreAllowed(bool(true, R.styleable.AndroidManifestApplication_allowClearUserDataOnFailedRestore, sa))
.setAllowNativeHeapPointerTagging(bool(true, R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, sa))
.setEnabled(bool(true, R.styleable.AndroidManifestApplication_enabled, sa))
- .setExtractNativeLibs(bool(true, R.styleable.AndroidManifestApplication_extractNativeLibs, sa))
- .setHasCode(bool(true, R.styleable.AndroidManifestApplication_hasCode, sa))
+ .setExtractNativeLibrariesRequested(bool(true, R.styleable.AndroidManifestApplication_extractNativeLibs, sa))
+ .setDeclaredHavingCode(bool(true, R.styleable.AndroidManifestApplication_hasCode, sa))
// Default false
- .setAllowTaskReparenting(bool(false, R.styleable.AndroidManifestApplication_allowTaskReparenting, sa))
- .setCantSaveState(bool(false, R.styleable.AndroidManifestApplication_cantSaveState, sa))
+ .setTaskReparentingAllowed(bool(false, R.styleable.AndroidManifestApplication_allowTaskReparenting, sa))
+ .setSaveStateDisallowed(bool(false, R.styleable.AndroidManifestApplication_cantSaveState, sa))
.setCrossProfile(bool(false, R.styleable.AndroidManifestApplication_crossProfile, sa))
.setDebuggable(bool(false, R.styleable.AndroidManifestApplication_debuggable, sa))
.setDefaultToDeviceProtectedStorage(bool(false, R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage, sa))
.setDirectBootAware(bool(false, R.styleable.AndroidManifestApplication_directBootAware, sa))
.setForceQueryable(bool(false, R.styleable.AndroidManifestApplication_forceQueryable, sa))
.setGame(bool(false, R.styleable.AndroidManifestApplication_isGame, sa))
- .setHasFragileUserData(bool(false, R.styleable.AndroidManifestApplication_hasFragileUserData, sa))
+ .setUserDataFragile(bool(false, R.styleable.AndroidManifestApplication_hasFragileUserData, sa))
.setLargeHeap(bool(false, R.styleable.AndroidManifestApplication_largeHeap, sa))
.setMultiArch(bool(false, R.styleable.AndroidManifestApplication_multiArch, sa))
.setPreserveLegacyExternalStorage(bool(false, R.styleable.AndroidManifestApplication_preserveLegacyExternalStorage, sa))
.setRequiredForAllUsers(bool(false, R.styleable.AndroidManifestApplication_requiredForAllUsers, sa))
- .setSupportsRtl(bool(false, R.styleable.AndroidManifestApplication_supportsRtl, sa))
+ .setRtlSupported(bool(false, R.styleable.AndroidManifestApplication_supportsRtl, sa))
.setTestOnly(bool(false, R.styleable.AndroidManifestApplication_testOnly, sa))
.setUseEmbeddedDex(bool(false, R.styleable.AndroidManifestApplication_useEmbeddedDex, sa))
- .setUsesNonSdkApi(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa))
+ .setNonSdkApiRequested(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa))
.setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa))
.setAutoRevokePermissions(anInt(R.styleable.AndroidManifestApplication_autoRevokePermissions, sa))
.setAttributionsAreUserVisible(bool(false, R.styleable.AndroidManifestApplication_attributionsAreUserVisible, sa))
@@ -2260,7 +2261,7 @@
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
.setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
- .setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
+ .setCleartextTrafficAllowed(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
// Ints Default 0
.setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
// Ints
@@ -2269,16 +2270,16 @@
.setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
.setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
// Resource ID
- .setBannerRes(resId(R.styleable.AndroidManifestApplication_banner, sa))
- .setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
- .setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
- .setLogoRes(resId(R.styleable.AndroidManifestApplication_logo, sa))
- .setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
- .setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
- .setThemeRes(resId(R.styleable.AndroidManifestApplication_theme, sa))
- .setDataExtractionRulesRes(
+ .setBannerResourceId(resId(R.styleable.AndroidManifestApplication_banner, sa))
+ .setDescriptionResourceId(resId(R.styleable.AndroidManifestApplication_description, sa))
+ .setIconResourceId(resId(R.styleable.AndroidManifestApplication_icon, sa))
+ .setLogoResourceId(resId(R.styleable.AndroidManifestApplication_logo, sa))
+ .setNetworkSecurityConfigResourceId(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
+ .setRoundIconResourceId(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
+ .setThemeResourceId(resId(R.styleable.AndroidManifestApplication_theme, sa))
+ .setDataExtractionRulesResourceId(
resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
- .setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
+ .setLocaleConfigResourceId(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
// Strings
.setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
.setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa))
@@ -2883,13 +2884,13 @@
// This is a trick to get a boolean and still able to detect
// if a value was actually set.
return input.success(pkg
- .setSupportsSmallScreens(
+ .setSmallScreensSupported(
anInt(1, R.styleable.AndroidManifestSupportsScreens_smallScreens, sa))
- .setSupportsNormalScreens(
+ .setNormalScreensSupported(
anInt(1, R.styleable.AndroidManifestSupportsScreens_normalScreens, sa))
- .setSupportsLargeScreens(
+ .setLargeScreensSupported(
anInt(1, R.styleable.AndroidManifestSupportsScreens_largeScreens, sa))
- .setSupportsExtraLargeScreens(
+ .setExtraLargeScreensSupported(
anInt(1, R.styleable.AndroidManifestSupportsScreens_xlargeScreens, sa))
.setResizeable(
anInt(1, R.styleable.AndroidManifestSupportsScreens_resizeable, sa))
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7f1679a..449801d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3501,8 +3501,8 @@
}
@Override
- public void onKeyguardOccludedChangedLw(boolean occluded) {
- if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
+ public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) {
+ if (mKeyguardDelegate != null && waitAppTransition) {
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index ca5fa5f..3c4dbf2 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -164,9 +164,10 @@
/**
* Called when the Keyguard occluded state changed.
+ *
* @param occluded Whether Keyguard is currently occluded or not.
*/
- void onKeyguardOccludedChangedLw(boolean occluded);
+ void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition);
/**
* @param notify {@code true} if the status change should be immediately notified via
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index 0dc5f76..f248a02 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -19,7 +19,6 @@
import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
import static android.os.PowerManager.lowPowerStandbyAllowedReasonsToString;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
@@ -692,8 +691,7 @@
final Intent intent = new Intent(
PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void onStandbyTimeoutExpired() {
@@ -812,7 +810,7 @@
}
}
- @NonNull
+ @Nullable
LowPowerStandbyPolicy getPolicy() {
synchronized (mLock) {
if (!mSupportedConfig) {
@@ -914,20 +912,22 @@
final int[] allowlistUids = getAllowlistUidsLocked();
ipw.print("Allowed UIDs=");
ipw.println(Arrays.toString(allowlistUids));
- ipw.println();
final LowPowerStandbyPolicy policy = getPolicy();
- ipw.println("mPolicy:");
- ipw.increaseIndent();
- ipw.print("mIdentifier=");
- ipw.println(policy.getIdentifier());
- ipw.print("mExemptPackages=");
- ipw.println(String.join(",", policy.getExemptPackages()));
- ipw.print("mAllowedReasons=");
- ipw.println(lowPowerStandbyAllowedReasonsToString(policy.getAllowedReasons()));
- ipw.print("mAllowedFeatures=");
- ipw.println(String.join(",", policy.getAllowedFeatures()));
- ipw.decreaseIndent();
+ if (policy != null) {
+ ipw.println();
+ ipw.println("mPolicy:");
+ ipw.increaseIndent();
+ ipw.print("mIdentifier=");
+ ipw.println(policy.getIdentifier());
+ ipw.print("mExemptPackages=");
+ ipw.println(String.join(",", policy.getExemptPackages()));
+ ipw.print("mAllowedReasons=");
+ ipw.println(lowPowerStandbyAllowedReasonsToString(policy.getAllowedReasons()));
+ ipw.print("mAllowedFeatures=");
+ ipw.println(String.join(",", policy.getAllowedFeatures()));
+ ipw.decreaseIndent();
+ }
ipw.println();
ipw.println("UID allowed reasons:");
@@ -967,17 +967,19 @@
proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId);
}
- long policyToken = proto.start(LowPowerStandbyControllerDumpProto.POLICY);
final LowPowerStandbyPolicy policy = getPolicy();
- proto.write(LowPowerStandbyPolicyProto.IDENTIFIER, policy.getIdentifier());
- for (String exemptPackage : policy.getExemptPackages()) {
- proto.write(LowPowerStandbyPolicyProto.EXEMPT_PACKAGES, exemptPackage);
+ if (policy != null) {
+ long policyToken = proto.start(LowPowerStandbyControllerDumpProto.POLICY);
+ proto.write(LowPowerStandbyPolicyProto.IDENTIFIER, policy.getIdentifier());
+ for (String exemptPackage : policy.getExemptPackages()) {
+ proto.write(LowPowerStandbyPolicyProto.EXEMPT_PACKAGES, exemptPackage);
+ }
+ proto.write(LowPowerStandbyPolicyProto.ALLOWED_REASONS, policy.getAllowedReasons());
+ for (String feature : policy.getAllowedFeatures()) {
+ proto.write(LowPowerStandbyPolicyProto.ALLOWED_FEATURES, feature);
+ }
+ proto.end(policyToken);
}
- proto.write(LowPowerStandbyPolicyProto.ALLOWED_REASONS, policy.getAllowedReasons());
- for (String feature : policy.getAllowedFeatures()) {
- proto.write(LowPowerStandbyPolicyProto.ALLOWED_FEATURES, feature);
- }
- proto.end(policyToken);
proto.end(token);
}
}
@@ -1047,6 +1049,9 @@
"Adding to allowlist: uid=" + uid + ", allowedReason=" + allowedReason);
}
synchronized (mLock) {
+ if (!mSupportedConfig) {
+ return;
+ }
if (allowedReason != 0 && !hasAllowedReasonLocked(uid, allowedReason)) {
addAllowedReasonLocked(uid, allowedReason);
if ((getPolicy().getAllowedReasons() & allowedReason) != 0) {
@@ -1062,6 +1067,9 @@
Slog.i(TAG, "Removing from allowlist: uid=" + uid + ", allowedReason=" + allowedReason);
}
synchronized (mLock) {
+ if (!mSupportedConfig) {
+ return;
+ }
if (allowedReason != 0 && hasAllowedReasonLocked(uid, allowedReason)) {
removeAllowedReasonLocked(uid, allowedReason);
if ((getPolicy().getAllowedReasons() & allowedReason) != 0) {
@@ -1077,6 +1085,9 @@
final PackageManager packageManager = mContext.getPackageManager();
final LowPowerStandbyPolicy policy = getPolicy();
final List<Integer> appIds = new ArrayList<>();
+ if (policy == null) {
+ return appIds;
+ }
for (String packageName : policy.getExemptPackages()) {
try {
@@ -1100,6 +1111,9 @@
final List<UserHandle> userHandles = userManager.getUserHandles(true);
final ArraySet<Integer> uids = new ArraySet<>(mUidAllowedReasons.size());
final LowPowerStandbyPolicy policy = getPolicy();
+ if (policy == null) {
+ return new int[0];
+ }
final int policyAllowedReasons = policy.getAllowedReasons();
for (int i = 0; i < mUidAllowedReasons.size(); i++) {
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index f17e5e7..41c2fbf 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -58,7 +58,7 @@
* @return The sensor handle.
*/
public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
- @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+ @NonNull String vendor, @NonNull RuntimeSensorCallback callback);
/**
* Unregisters the sensor with the given handle from the framework.
@@ -95,11 +95,12 @@
* {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
* not covered.
*/
- public interface RuntimeSensorStateChangeCallback {
+ public interface RuntimeSensorCallback {
/**
* Invoked when the listeners of the runtime sensor have changed.
+ * Returns an error code if the invocation was unsuccessful, zero otherwise.
*/
- void onStateChanged(boolean enabled, int samplingPeriodMicros,
+ int onConfigurationChanged(int handle, boolean enabled, int samplingPeriodMicros,
int batchReportLatencyMicros);
}
}
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index d8e3bdd..9790659 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -56,8 +56,7 @@
private static native void unregisterProximityActiveListenerNative(long ptr);
private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
- String name, String vendor,
- SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+ String name, String vendor, SensorManagerInternal.RuntimeSensorCallback callback);
private static native void unregisterRuntimeSensorNative(long ptr, int handle);
private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
long timestampNanos, float[] values);
@@ -96,7 +95,7 @@
class LocalService extends SensorManagerInternal {
@Override
public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
- @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+ @NonNull String vendor, @NonNull RuntimeSensorCallback callback) {
synchronized (mLock) {
int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
callback);
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index e148a48..4839c96 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -233,7 +233,9 @@
void checkRecognitionSupport(
Intent recognizerIntent,
+ AttributionSource attributionSource,
IRecognitionSupportCallback callback) {
+
if (!mConnected) {
try {
callback.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
@@ -243,15 +245,16 @@
}
return;
}
- run(service -> service.checkRecognitionSupport(recognizerIntent, callback));
+ run(service ->
+ service.checkRecognitionSupport(recognizerIntent, attributionSource, callback));
}
- void triggerModelDownload(Intent recognizerIntent) {
+ void triggerModelDownload(Intent recognizerIntent, AttributionSource attributionSource) {
if (!mConnected) {
Slog.e(TAG, "#downloadModel failed due to connection.");
return;
}
- run(service -> service.triggerModelDownload(recognizerIntent));
+ run(service -> service.triggerModelDownload(recognizerIntent, attributionSource));
}
void shutdown(IBinder clientToken) {
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index e245c08..6aa600a 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -183,13 +183,17 @@
@Override
public void checkRecognitionSupport(
Intent recognizerIntent,
+ AttributionSource attributionSource,
IRecognitionSupportCallback callback) {
- service.checkRecognitionSupport(recognizerIntent, callback);
+ service.checkRecognitionSupport(
+ recognizerIntent, attributionSource, callback);
}
@Override
- public void triggerModelDownload(Intent recognizerIntent) {
- service.triggerModelDownload(recognizerIntent);
+ public void triggerModelDownload(
+ Intent recognizerIntent,
+ AttributionSource attributionSource) {
+ service.triggerModelDownload(recognizerIntent, attributionSource);
}
});
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 5801920..fc960d8 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -129,4 +129,9 @@
public void dumpDebugLog(@NonNull PrintWriter printWriter) {
SystemClockTime.dump(printWriter);
}
+
+ @Override
+ public void runAsync(@NonNull Runnable runnable) {
+ mHandler.post(runnable);
+ }
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java b/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java
index 5df5cbc..4b65c55 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java
@@ -17,10 +17,13 @@
package com.android.server.timedetector;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
import android.app.timedetector.ManualTimeSuggestion;
+import com.android.server.timezonedetector.StateChangeListener;
+
/**
* The internal (in-process) system server API for the time detector service.
*
@@ -61,11 +64,26 @@
/**
* Suggests a network time to the time detector. The suggestion may not be used by the time
* detector to set the device's time depending on device configuration and user settings, but
- * can replace previous network suggestions received.
+ * can replace previous network suggestions received. See also
+ * {@link #addNetworkTimeUpdateListener(StateChangeListener)} and
+ * {@link #getLatestNetworkSuggestion()}.
*/
void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion);
/**
+ * Adds a listener that will be notified when a new network time is available. See {@link
+ * #getLatestNetworkSuggestion()}.
+ */
+ void addNetworkTimeUpdateListener(
+ @NonNull StateChangeListener networkSuggestionUpdateListener);
+
+ /**
+ * Returns the latest / best network time received by the time detector.
+ */
+ @Nullable
+ NetworkTimeSuggestion getLatestNetworkSuggestion();
+
+ /**
* Suggests a GNSS-derived time to the time detector. The suggestion may not be used by the time
* detector to set the device's time depending on device configuration and user settings, but
* can replace previous GNSS suggestions received.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java
index af168f8..7e96a43 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java
@@ -24,6 +24,7 @@
import android.os.Handler;
import com.android.server.timezonedetector.CurrentUserIdentityInjector;
+import com.android.server.timezonedetector.StateChangeListener;
import java.util.Objects;
@@ -87,6 +88,19 @@
}
@Override
+ public void addNetworkTimeUpdateListener(
+ @NonNull StateChangeListener networkTimeUpdateListener) {
+ Objects.requireNonNull(networkTimeUpdateListener);
+ mTimeDetectorStrategy.addNetworkTimeUpdateListener(networkTimeUpdateListener);
+ }
+
+ @Override
+ @NonNull
+ public NetworkTimeSuggestion getLatestNetworkSuggestion() {
+ return mTimeDetectorStrategy.getLatestNetworkSuggestion();
+ }
+
+ @Override
public void suggestGnssTime(@NonNull GnssTimeSuggestion suggestion) {
Objects.requireNonNull(suggestion);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index a9dcff4..0da967a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -47,6 +47,7 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
+import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper;
import com.android.server.timezonedetector.CallerIdentityInjector;
import com.android.server.timezonedetector.CurrentUserIdentityInjector;
@@ -405,13 +406,19 @@
// TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
// be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
// detector. mNtpTrustedTime can be removed once this happens.
- NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
- if (ntpResult != null) {
- UnixEpochTime unixEpochTime = new UnixEpochTime(
- ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
- return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ // The new implementation.
+ return mTimeDetectorStrategy.getLatestNetworkSuggestion();
} else {
- return null;
+ // The old implementation.
+ NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+ if (ntpResult != null) {
+ UnixEpochTime unixEpochTime = new UnixEpochTime(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+ return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
+ } else {
+ return null;
+ }
}
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index dbd7172..11cec66 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -29,6 +29,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.timezonedetector.Dumpable;
+import com.android.server.timezonedetector.StateChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -104,11 +105,19 @@
/**
* Processes the suggested network time. The suggestion may not be used to set the device's time
* depending on device configuration and user settings, but can replace previous network
- * suggestions received.
+ * suggestions received. See also
+ * {@link #addNetworkTimeUpdateListener(StateChangeListener)} and
+ * {@link #getLatestNetworkSuggestion()}.
*/
void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion);
/**
+ * Adds a listener that will be notified when a new network time is available. See {@link
+ * #getLatestNetworkSuggestion()}.
+ */
+ void addNetworkTimeUpdateListener(@NonNull StateChangeListener networkSuggestionUpdateListener);
+
+ /**
* Returns the latest (accepted) network time suggestion. Returns {@code null} if there isn't
* one.
*/
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index d679bbe..b293bac 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -36,6 +36,7 @@
import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
import android.os.Handler;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -125,6 +126,9 @@
private final ReferenceWithHistory<ExternalTimeSuggestion> mLastExternalSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ @GuardedBy("this")
+ private final ArraySet<StateChangeListener> mNetworkTimeUpdateListeners = new ArraySet<>();
+
/**
* Used by {@link TimeDetectorStrategyImpl} to interact with device configuration / settings
* / system properties. It can be faked for testing.
@@ -180,6 +184,11 @@
* Dumps the time debug log to the supplied {@link PrintWriter}.
*/
void dumpDebugLog(PrintWriter printWriter);
+
+ /**
+ * Requests that the supplied runnable is invoked asynchronously.
+ */
+ void runAsync(@NonNull Runnable runnable);
}
static TimeDetectorStrategy create(
@@ -307,6 +316,7 @@
NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get();
if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(suggestion)) {
mLastNetworkSuggestion.set(suggestion);
+ notifyNetworkTimeUpdateListenersAsynchronously();
}
// Now perform auto time detection. The new suggestion may be used to modify the system
@@ -315,6 +325,20 @@
doAutoTimeDetection(reason);
}
+ @GuardedBy("this")
+ private void notifyNetworkTimeUpdateListenersAsynchronously() {
+ for (StateChangeListener listener : mNetworkTimeUpdateListeners) {
+ // This is queuing asynchronous notification, so no need to surrender the "this" lock.
+ mEnvironment.runAsync(listener::onChange);
+ }
+ }
+
+ @Override
+ public synchronized void addNetworkTimeUpdateListener(
+ @NonNull StateChangeListener networkSuggestionUpdateListener) {
+ mNetworkTimeUpdateListeners.add(networkSuggestionUpdateListener);
+ }
+
@Override
@Nullable
public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() {
@@ -325,6 +349,8 @@
public synchronized void clearLatestNetworkSuggestion() {
mLastNetworkSuggestion.set(null);
+ notifyNetworkTimeUpdateListenersAsynchronously();
+
// The loss of network time may change the time signal to use to set the system clock.
String reason = "Network time cleared";
doAutoTimeDetection(reason);
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 5fd70a8..fc659c5 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -104,10 +104,10 @@
}
/**
- * Returns {@code true} if location time zone detection should run all the time on supported
- * devices, even when the user has not enabled it explicitly in settings. Enabled for internal
- * testing only. See {@link #isGeoDetectionExecutionEnabled()} and {@link #getDetectionMode()}
- * for details.
+ * Returns {@code true} if location time zone detection should run when auto time zone detection
+ * is enabled on supported devices, even when the user has not enabled the algorithm explicitly
+ * in settings. Enabled for internal testing only. See {@link #isGeoDetectionExecutionEnabled()}
+ * and {@link #getDetectionMode()} for details.
*/
boolean getGeoDetectionRunInBackgroundEnabledSetting() {
return mGeoDetectionRunInBackgroundEnabled;
@@ -219,6 +219,7 @@
private boolean getGeoDetectionRunInBackgroundEnabledBehavior() {
return isGeoDetectionSupported()
&& getLocationEnabledSetting()
+ && getAutoDetectionEnabledSetting()
&& getGeoDetectionRunInBackgroundEnabledSetting();
}
@@ -433,9 +434,9 @@
}
/**
- * Sets whether location time zone detection should run all the time on supported devices,
- * even when the user has not enabled it explicitly in settings. Enabled for internal
- * testing only.
+ * Sets whether location time zone detection should run when auto time zone detection is
+ * enabled on supported devices, even when the user has not enabled the algorithm explicitly
+ * in settings. Enabled for internal testing only.
*/
public Builder setGeoDetectionRunInBackgroundEnabled(boolean enabled) {
mGeoDetectionRunInBackgroundEnabled = enabled;
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index f829449..61c137e 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1140,7 +1140,8 @@
@Override
- public void notifyRecordingStarted(IBinder sessionToken, String recordingId, int userId) {
+ public void notifyRecordingStarted(IBinder sessionToken, String recordingId,
+ String requestId, int userId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
@@ -1151,7 +1152,8 @@
try {
SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
resolvedUserId);
- getSessionLocked(sessionState).notifyRecordingStarted(recordingId);
+ getSessionLocked(sessionState).notifyRecordingStarted(
+ recordingId, requestId);
} catch (RemoteException | SessionNotFoundException e) {
Slogf.e(TAG, "error in notifyRecordingStarted", e);
}
@@ -2831,7 +2833,7 @@
}
@Override
- public void onRequestStartRecording(Uri programUri) {
+ public void onRequestStartRecording(String requestId, Uri programUri) {
synchronized (mLock) {
if (DEBUG) {
Slogf.d(TAG, "onRequestStartRecording: " + programUri);
@@ -2840,7 +2842,8 @@
return;
}
try {
- mSessionState.mClient.onRequestStartRecording(programUri, mSessionState.mSeq);
+ mSessionState.mClient.onRequestStartRecording(
+ requestId, programUri, mSessionState.mSeq);
} catch (RemoteException e) {
Slogf.e(TAG, "error in onRequestStartRecording", e);
}
@@ -2866,7 +2869,7 @@
@Override
public void onRequestScheduleRecording(
- String inputId, Uri channelUri, Uri programUri, Bundle params) {
+ String requestId, String inputId, Uri channelUri, Uri programUri, Bundle params) {
synchronized (mLock) {
if (DEBUG) {
Slogf.d(TAG, "onRequestScheduleRecording");
@@ -2876,7 +2879,7 @@
}
try {
mSessionState.mClient.onRequestScheduleRecording(
- inputId, channelUri, programUri, params, mSessionState.mSeq);
+ requestId, inputId, channelUri, programUri, params, mSessionState.mSeq);
} catch (RemoteException e) {
Slogf.e(TAG, "error in onRequestScheduleRecording", e);
}
@@ -2884,8 +2887,8 @@
}
@Override
- public void onRequestScheduleRecording2(String inputId, Uri channelUri, long start,
- long duration, int repeat, Bundle params) {
+ public void onRequestScheduleRecording2(String inputId, String requestId, Uri channelUri,
+ long start, long duration, int repeat, Bundle params) {
synchronized (mLock) {
if (DEBUG) {
Slogf.d(TAG, "onRequestScheduleRecording2");
@@ -2894,8 +2897,8 @@
return;
}
try {
- mSessionState.mClient.onRequestScheduleRecording2(inputId, channelUri, start,
- duration, repeat, params, mSessionState.mSeq);
+ mSessionState.mClient.onRequestScheduleRecording2(requestId, inputId,
+ channelUri, start, duration, repeat, params, mSessionState.mSeq);
} catch (RemoteException e) {
Slogf.e(TAG, "error in onRequestScheduleRecording2", e);
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 502bfd1..7a95987 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1382,6 +1382,31 @@
}
@Override
+ public void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim,
+ int backgroundColor) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+ if (r != null) {
+ r.overrideCustomTransition(open, enterAnim, exitAnim, backgroundColor);
+ }
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ @Override
+ public void clearOverrideActivityTransition(IBinder token, boolean open) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+ if (r != null) {
+ r.clearCustomTransition(open);
+ }
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ @Override
public void overridePendingTransition(IBinder token, String packageName,
int enterAnim, int exitAnim, @ColorInt int backgroundColor) {
final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index ff1d442..d844c6f 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -266,7 +266,7 @@
* @param resolvedType the resolved type.
*/
@NonNull
- public Builder setResolvedType(@NonNull String resolvedType) {
+ public Builder setResolvedType(@Nullable String resolvedType) {
mResolvedType = resolvedType;
return this;
}
@@ -276,7 +276,7 @@
* @param callingPackage the calling package.
*/
@NonNull
- public Builder setCallingPackage(@NonNull String callingPackage) {
+ public Builder setCallingPackage(@Nullable String callingPackage) {
mCallingPackage = callingPackage;
return this;
}
@@ -286,7 +286,7 @@
* @param callingFeatureId the calling feature id.
*/
@NonNull
- public Builder setCallingFeatureId(@NonNull String callingFeatureId) {
+ public Builder setCallingFeatureId(@Nullable String callingFeatureId) {
mCallingFeatureId = callingFeatureId;
return this;
}
@@ -296,7 +296,7 @@
* @param checkedOptions the {@link ActivityOptions}.
*/
@NonNull
- public Builder setCheckedOptions(@NonNull ActivityOptions checkedOptions) {
+ public Builder setCheckedOptions(@Nullable ActivityOptions checkedOptions) {
mCheckedOptions = checkedOptions;
return this;
}
@@ -306,7 +306,7 @@
* @param clearOptionsAnimationRunnable the calling package.
*/
@NonNull
- public Builder setClearOptionsAnimationRunnable(@NonNull
+ public Builder setClearOptionsAnimationRunnable(@Nullable
Runnable clearOptionsAnimationRunnable) {
mClearOptionsAnimation = clearOptionsAnimationRunnable;
return this;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 81ee9cd..828848b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -945,6 +945,10 @@
// Whether the ActivityEmbedding is enabled on the app.
private final boolean mAppActivityEmbeddingSplitsEnabled;
+ // Records whether client has overridden the WindowAnimation_(Open/Close)(Enter/Exit)Animation.
+ private CustomAppTransition mCustomOpenTransition;
+ private CustomAppTransition mCustomCloseTransition;
+
private final Runnable mPauseTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -10352,6 +10356,41 @@
&& !inPinnedWindowingMode() && !inFreeformWindowingMode();
}
+ void overrideCustomTransition(boolean open, int enterAnim, int exitAnim, int backgroundColor) {
+ CustomAppTransition transition = getCustomAnimation(open);
+ if (transition == null) {
+ transition = new CustomAppTransition();
+ if (open) {
+ mCustomOpenTransition = transition;
+ } else {
+ mCustomCloseTransition = transition;
+ }
+ }
+
+ transition.mEnterAnim = enterAnim;
+ transition.mExitAnim = exitAnim;
+ transition.mBackgroundColor = backgroundColor;
+ }
+
+ void clearCustomTransition(boolean open) {
+ if (open) {
+ mCustomOpenTransition = null;
+ } else {
+ mCustomCloseTransition = null;
+ }
+ }
+
+ CustomAppTransition getCustomAnimation(boolean open) {
+ return open ? mCustomOpenTransition : mCustomCloseTransition;
+ }
+
+ // Override the WindowAnimation_(Open/Close)(Enter/Exit)Animation
+ static class CustomAppTransition {
+ int mEnterAnim;
+ int mExitAnim;
+ int mBackgroundColor;
+ }
+
static class Builder {
private final ActivityTaskManagerService mAtmService;
private WindowProcessController mCallerApp;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c37a3d7..9ca2015 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1967,7 +1967,7 @@
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
/* caller_uid */
- mSourceRecord != null ? mSourceRecord.getUid() : -1,
+ mSourceRecord != null ? mSourceRecord.getUid() : mCallingUid,
/* caller_activity_class_name */
mSourceRecord != null ? mSourceRecord.info.name : null,
/* target_task_top_activity_uid */
@@ -1988,10 +1988,12 @@
/* action */
action,
/* version */
- 2,
+ 3,
/* multi_window - we have our source not in the target task, but both are visible */
targetTask != null && mSourceRecord != null
- && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible()
+ && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(),
+ /* bal_code */
+ mBalCode
);
boolean shouldBlockActivityStart =
@@ -1999,19 +2001,20 @@
if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (shouldBlockActivityStart
- ? "Activity start blocked by "
- : "Activity start would be blocked by ")
- + ActivitySecurityModelFeatureFlags.DOC_LINK,
+ "Activity start from " + r.launchedFromPackage
+ + (shouldBlockActivityStart ? " " : " would be ")
+ + "blocked by " + ActivitySecurityModelFeatureFlags.DOC_LINK,
Toast.LENGTH_SHORT).show());
}
if (shouldBlockActivityStart) {
Slog.e(TAG, "Abort Launching r: " + r
- + " as source: " + mSourceRecord
- + "is in background. New task: " + newTask
- + ". Top activity: " + targetTopActivity);
+ + " as source: "
+ + (mSourceRecord != null ? mSourceRecord : r.launchedFromPackage)
+ + " is in background. New task: " + newTask
+ + ". Top activity: " + targetTopActivity
+ + ". BAL Code: " + mBalCode);
return false;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2869133..3876290 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1667,9 +1667,11 @@
/* action */
FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
/* version */
- 1,
+ 3,
/* multi_window */
- false
+ false,
+ /* bal_code */
+ -1
);
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index b9a4ed8..f73c68a 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -142,6 +142,7 @@
import com.android.internal.util.DumpUtils.Dump;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.wm.ActivityRecord.CustomAppTransition;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -886,9 +887,18 @@
a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
} else {
int animAttr = mapOpenCloseTransitTypes(transit, enter);
- a = animAttr == 0 ? null : (canCustomizeAppTransition
- ? loadAnimationAttr(lp, animAttr, transit)
- : mTransitionAnimation.loadDefaultAnimationAttr(animAttr, transit));
+ if (animAttr != 0) {
+ a = loadCustomActivityAnimation(animAttr, enter, container);
+ if (a == null) {
+ if (canCustomizeAppTransition) {
+ a = loadAnimationAttr(lp, animAttr, transit);
+ } else {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, transit);
+ }
+ }
+ } else {
+ a = null;
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
@@ -901,6 +911,48 @@
return a;
}
+ Animation loadCustomActivityAnimation(int animAttr, boolean enter, WindowContainer container) {
+ ActivityRecord customAnimationSource = container.asActivityRecord();
+ if (customAnimationSource == null) {
+ return null;
+ }
+
+ // Only top activity can customize activity animation.
+ // If the animation is for the one below, try to get from the above activity.
+ if (animAttr == WindowAnimation_activityOpenExitAnimation
+ || animAttr == WindowAnimation_activityCloseEnterAnimation) {
+ customAnimationSource = customAnimationSource.getTask()
+ .getActivityAbove(customAnimationSource);
+ if (customAnimationSource == null) {
+ return null;
+ }
+ }
+ final CustomAppTransition custom;
+ switch (animAttr) {
+ case WindowAnimation_activityOpenEnterAnimation:
+ case WindowAnimation_activityOpenExitAnimation:
+ custom = customAnimationSource.getCustomAnimation(true /* open */);
+ break;
+ case WindowAnimation_activityCloseEnterAnimation:
+ case WindowAnimation_activityCloseExitAnimation:
+ custom = customAnimationSource.getCustomAnimation(false /* open */);
+ break;
+ default:
+ return null;
+ }
+ if (custom != null) {
+ final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+ customAnimationSource.packageName, enter
+ ? custom.mEnterAnim : custom.mExitAnim);
+ if (a != null && custom.mBackgroundColor != 0) {
+ a.setBackdropColor(custom.mBackgroundColor);
+ a.setShowBackdrop(true);
+ }
+ return a;
+ }
+ return null;
+ }
+
int getAppRootTaskClipMode() {
return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH)
|| mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY)
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 63dc7d2..f94fd2b 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -102,6 +102,41 @@
boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
long lastStopAppSwitchesTime, long lastActivityLaunchTime,
long lastActivityFinishTime) {
+ // Allow if the proc is instrumenting with background activity starts privs.
+ if (hasBackgroundActivityStartPrivileges) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "[Process(" + pid
+ + ")] Activity start allowed: process instrumenting with background "
+ + "activity starts privileges");
+ }
+ return BAL_ALLOW_BAL_PERMISSION;
+ }
+ // Allow if the flag was explicitly set.
+ if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "[Process(" + pid
+ + ")] Activity start allowed: process allowed by token");
+ }
+ return BAL_ALLOW_BAL_PERMISSION;
+ }
+ // Allow if the caller is bound by a UID that's currently foreground.
+ if (isBoundByForegroundUid()) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "[Process(" + pid
+ + ")] Activity start allowed: process bound by foreground uid");
+ }
+ return BAL_ALLOW_VISIBLE_WINDOW;
+ }
+ // Allow if the caller has an activity in any foreground task.
+ if (hasActivityInVisibleTask
+ && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "[Process(" + pid
+ + ")] Activity start allowed: process has activity in foreground task");
+ }
+ return BAL_ALLOW_FOREGROUND;
+ }
+
// If app switching is not allowed, we ignore all the start activity grace period
// exception so apps cannot start itself in onPause() after pressing home button.
if (appSwitchState == APP_SWITCH_ALLOW) {
@@ -129,40 +164,6 @@
}
}
- // Allow if the proc is instrumenting with background activity starts privs.
- if (hasBackgroundActivityStartPrivileges) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[Process(" + pid
- + ")] Activity start allowed: process instrumenting with background "
- + "activity starts privileges");
- }
- return BAL_ALLOW_BAL_PERMISSION;
- }
- // Allow if the caller has an activity in any foreground task.
- if (hasActivityInVisibleTask
- && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[Process(" + pid
- + ")] Activity start allowed: process has activity in foreground task");
- }
- return BAL_ALLOW_FOREGROUND;
- }
- // Allow if the caller is bound by a UID that's currently foreground.
- if (isBoundByForegroundUid()) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[Process(" + pid
- + ")] Activity start allowed: process bound by foreground uid");
- }
- return BAL_ALLOW_VISIBLE_WINDOW;
- }
- // Allow if the flag was explicitly set.
- if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[Process(" + pid
- + ")] Activity start allowed: process allowed by token");
- }
- return BAL_ALLOW_BAL_PERMISSION;
- }
return BAL_BLOCK;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fe9306b..626bac4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1014,6 +1014,9 @@
mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing
|= w.mAttrs.preferMinimalPostProcessing;
+ mTmpApplySurfaceChangesTransactionState.disableHdrConversion
+ |= !(w.mAttrs.isHdrConversionEnabled());
+
final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(w);
@@ -4891,6 +4894,7 @@
mTmpApplySurfaceChangesTransactionState.preferredMinRefreshRate,
mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate,
mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
+ mTmpApplySurfaceChangesTransactionState.disableHdrConversion,
true /* inTraversal, must call performTraversalInTrans... below */);
}
// If the display now has content, or no longer has content, update recording.
@@ -5095,6 +5099,7 @@
public int preferredModeId;
public float preferredMinRefreshRate;
public float preferredMaxRefreshRate;
+ public boolean disableHdrConversion;
void reset() {
displayHasContent = false;
@@ -5105,6 +5110,7 @@
preferredModeId = 0;
preferredMinRefreshRate = 0;
preferredMaxRefreshRate = 0;
+ disableHdrConversion = false;
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index e9badef..32b3ccf 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -401,8 +401,10 @@
return;
}
- mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
- if (isKeyguardLocked(displayId)) {
+ final boolean waitAppTransition = isKeyguardLocked(displayId);
+ mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY),
+ waitAppTransition);
+ if (waitAppTransition) {
mService.deferWindowLayout();
try {
mRootWindowContainer.getDefaultDisplay()
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 294e90b..c2afeaf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -904,37 +904,46 @@
* starting (about to be visible) activity that is fullscreen (opaque).
* @param starting The currently starting activity or null if there is none.
*/
- @VisibleForTesting
- boolean isTranslucent(ActivityRecord starting) {
+ boolean isTranslucent(@Nullable ActivityRecord starting) {
if (!isAttached() || isForceHidden() || isForceTranslucent()) {
return true;
}
final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
- PooledLambda.__(ActivityRecord.class), starting);
+ PooledLambda.__(ActivityRecord.class), starting, false /* including*/);
final ActivityRecord opaque = getActivity(p);
p.recycle();
return opaque == null;
}
- private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
- if (r.finishing) {
- // We don't factor in finishing activities when determining translucency since
- // they will be gone soon.
- return false;
+ /**
+ * Whether the TaskFragment should be treated as translucent for the current transition.
+ * This is different from {@link #isTranslucent(ActivityRecord)} as this function also checks
+ * finishing activities when the TaskFragment itself is becoming invisible.
+ */
+ boolean isTranslucentForTransition() {
+ if (!isAttached() || isForceHidden() || isForceTranslucent()) {
+ return true;
}
+ // Including finishing Activity if the TaskFragment is becoming invisible in the transition.
+ final boolean includingFinishing = !isVisibleRequested();
+ final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
+ PooledLambda.__(ActivityRecord.class), null /* starting */, includingFinishing);
+ final ActivityRecord opaque = getActivity(p);
+ p.recycle();
+ return opaque == null;
+ }
+ private static boolean isOpaqueActivity(@NonNull ActivityRecord r,
+ @Nullable ActivityRecord starting, boolean includingFinishing) {
if (!r.visibleIgnoringKeyguard && r != starting) {
// Also ignore invisible activities that are not the currently starting
// activity (about to be visible).
return false;
}
- if (r.occludesParent()) {
- // Root task isn't translucent if it has at least one fullscreen activity
- // that is visible.
- return true;
- }
- return false;
+ // TaskFragment isn't translucent if it has at least one fullscreen activity that is
+ // visible.
+ return r.occludesParent(includingFinishing);
}
ActivityRecord getTopNonFinishingActivity() {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d23b954..216544a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1447,22 +1447,25 @@
private static boolean isTranslucent(@NonNull WindowContainer wc) {
final TaskFragment taskFragment = wc.asTaskFragment();
- if (taskFragment != null) {
- if (taskFragment.isTranslucent(null /* starting */)) {
- return true;
- }
- final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- if (adjacentTaskFragment != null) {
- // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything
- // behind two adjacent TaskFragments are occluded.
- return adjacentTaskFragment.isTranslucent(null /* starting */);
- }
+ if (taskFragment == null) {
+ return !wc.fillsParent();
}
- // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
- // it is effected by child visibility but needs to work even
- // before visibility is committed. This means refactoring some
- // checks to use requested visibility.
- return !wc.fillsParent();
+
+ // Check containers differently as they are affected by child visibility.
+
+ if (taskFragment.isTranslucentForTransition()) {
+ // TaskFragment doesn't contain occluded ActivityRecord.
+ return true;
+ }
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTaskFragment != null) {
+ // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
+ // hidden unless any of them are translucent.
+ return adjacentTaskFragment.isTranslucentForTransition();
+ } else {
+ // Non-filling without adjacent is considered as translucent.
+ return !wc.fillsParent();
+ }
}
/**
@@ -1878,6 +1881,12 @@
out.addChange(change);
}
+ TransitionInfo.AnimationOptions animOptions = null;
+ if (topApp.asActivityRecord() != null) {
+ final ActivityRecord topActivity = topApp.asActivityRecord();
+ animOptions = addCustomActivityTransition(topActivity, true/* open */, null);
+ animOptions = addCustomActivityTransition(topActivity, false/* open */, animOptions);
+ }
final WindowManager.LayoutParams animLp =
getLayoutParamsForAnimationsStyle(type, sortedTargets);
if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
@@ -1885,14 +1894,33 @@
// Don't send animation options if no windowAnimations have been set or if the we are
// running an app starting animation, in which case we don't want the app to be able to
// change its animation directly.
- TransitionInfo.AnimationOptions animOptions =
- TransitionInfo.AnimationOptions.makeAnimOptionsFromLayoutParameters(animLp);
+ if (animOptions != null) {
+ animOptions.addOptionsFromLayoutParameters(animLp);
+ } else {
+ animOptions = TransitionInfo.AnimationOptions
+ .makeAnimOptionsFromLayoutParameters(animLp);
+ }
+ }
+ if (animOptions != null) {
out.setAnimationOptions(animOptions);
}
-
return out;
}
+ static TransitionInfo.AnimationOptions addCustomActivityTransition(ActivityRecord topActivity,
+ boolean open, TransitionInfo.AnimationOptions animOptions) {
+ final ActivityRecord.CustomAppTransition customAnim =
+ topActivity.getCustomAnimation(open);
+ if (customAnim != null) {
+ if (animOptions == null) {
+ animOptions = TransitionInfo.AnimationOptions
+ .makeCommonAnimOptions(topActivity.packageName);
+ }
+ animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
+ customAnim.mExitAnim, customAnim.mBackgroundColor);
+ }
+ return animOptions;
+ }
/**
* Finds the top-most common ancestor of app targets.
*
@@ -2019,7 +2047,13 @@
final DisplayContent dc = wc.asDisplayContent();
if (dc == null || !mChanges.get(dc).hasChanged()) continue;
dc.sendNewConfiguration();
- setReady(dc, true);
+ // Set to ready if no other change controls the ready state. But if there is, such as
+ // if an activity is pausing, it will call setReady(ar, false) and wait for the next
+ // resumed activity. Then do not set to ready because the transition only contains
+ // partial participants. Otherwise the transition may only handle HIDE and miss OPEN.
+ if (!mReadyTracker.mUsed) {
+ setReady(dc, true);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a63f9a3..ae7dc1e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -193,6 +193,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -744,7 +745,7 @@
private final DisplayHashController mDisplayHashController;
volatile float mMaximumObscuringOpacityForTouch =
- InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH;
+ InputSettings.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH;
@VisibleForTesting
final WindowContextListenerController mWindowContextListenerController =
@@ -886,11 +887,11 @@
ContentResolver resolver = mContext.getContentResolver();
mMaximumObscuringOpacityForTouch = Settings.Global.getFloat(resolver,
Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
- InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
+ InputSettings.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
if (mMaximumObscuringOpacityForTouch < 0.0f
|| mMaximumObscuringOpacityForTouch > 1.0f) {
mMaximumObscuringOpacityForTouch =
- InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH;
+ InputSettings.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH;
}
}
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index db7fced..77e8324 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -110,6 +110,15 @@
return array;
}
+static jboolean nativeGetHdrOutputConversionSupport(JNIEnv* env, jclass clazz) {
+ bool isSupported;
+ status_t err = SurfaceComposerClient::getHdrOutputConversionSupport(&isSupported);
+ if (err == OK) {
+ return isSupported;
+ }
+ return JNI_FALSE;
+}
+
static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
ScopedLongArrayRW values(env, env->NewLongArray(displayIds.size()));
@@ -150,6 +159,8 @@
(void*)nativeSetHdrConversionMode },
{"nativeGetSupportedHdrOutputTypes", "()[I",
(void*)nativeGetSupportedHdrOutputTypes },
+ {"nativeGetHdrOutputConversionSupport", "()Z",
+ (void*) nativeGetHdrOutputConversionSupport },
// clang-format on
};
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 10d8b42..356e9a9 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -32,13 +32,13 @@
"com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
#define RUNTIME_SENSOR_CALLBACK_CLASS \
- "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+ "com/android/server/sensors/SensorManagerInternal$RuntimeSensorCallback"
namespace android {
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnProximityActive;
-static jmethodID sMethodIdOnStateChanged;
+static jmethodID sMethodIdOnConfigurationChanged;
class NativeSensorService {
public:
@@ -67,13 +67,13 @@
};
sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
- class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+ class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorCallback {
public:
RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
~RuntimeSensorCallbackDelegate();
- void onStateChanged(bool enabled, int64_t samplingPeriodNs,
- int64_t batchReportLatencyNs) override;
+ status_t onConfigurationChanged(int32_t handle, bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) override;
private:
jobject mCallback;
@@ -231,12 +231,13 @@
AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
}
-void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
- bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+status_t NativeSensorService::RuntimeSensorCallbackDelegate::onConfigurationChanged(
+ int32_t handle, bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
- jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
- static_cast<jint>(ns2us(samplingPeriodNs)),
- static_cast<jint>(ns2us(batchReportLatencyNs)));
+ return jniEnv->CallIntMethod(mCallback, sMethodIdOnConfigurationChanged,
+ static_cast<jint>(handle), static_cast<jboolean>(enabled),
+ static_cast<jint>(ns2us(samplingPeriodNs)),
+ static_cast<jint>(ns2us(batchReportLatencyNs)));
}
static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
@@ -292,8 +293,8 @@
jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
- sMethodIdOnStateChanged =
- GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
+ sMethodIdOnConfigurationChanged =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onConfigurationChanged", "(IZII)I");
return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
NELEM(methods));
}
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 447c67f..d262fb7 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -83,7 +83,10 @@
}
@Override
- public void onFinalResponseReceived(ComponentName componentName, Void response) {
+ public void onFinalResponseReceived(
+ ComponentName componentName,
+ Void response) {
+ setChosenMetric(componentName);
respondToClientWithResponseAndFinish();
}
@@ -114,6 +117,8 @@
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
// TODO: Differentiate btw cancelled and false
+ mChosenProviderMetric.setChosenProviderStatus(
+ MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
finishSession(/*propagateCancellation=*/true);
return;
@@ -122,6 +127,8 @@
mClientCallback.onSuccess();
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
} catch (RemoteException e) {
+ mChosenProviderMetric.setChosenProviderStatus(
+ MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE);
Log.i(TAG, "Issue while propagating the response to the client");
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index c8518c5..01e7fb9 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -16,6 +16,9 @@
package com.android.server.credentials;
+import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
+import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -94,9 +97,14 @@
public void onFinalResponseReceived(ComponentName componentName,
@Nullable CreateCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ setChosenMetric(componentName);
if (response != null) {
+ mChosenProviderMetric.setChosenProviderStatus(
+ METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
respondToClientWithResponseAndFinish(response);
} else {
+ mChosenProviderMetric.setChosenProviderStatus(
+ METRICS_PROVIDER_STATUS_FINAL_FAILURE);
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 34101fe..0334051 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -357,7 +357,6 @@
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(
@@ -497,6 +496,7 @@
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);
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 3324999..14b45b2 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,6 +16,9 @@
package com.android.server.credentials;
+import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
+import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
+
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -87,9 +90,14 @@
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ setChosenMetric(componentName);
if (response != null) {
+ mChosenProviderMetric.setChosenProviderStatus(
+ METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
respondToClientWithResponseAndFinish(response);
} else {
+ mChosenProviderMetric.setChosenProviderStatus(
+ METRICS_PROVIDER_STATUS_FINAL_FAILURE);
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
}
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
new file mode 100644
index 0000000..27d9836d
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+/**
+ * For all future metric additions, this will contain their names for local usage after importing
+ * from {@link com.android.internal.util.FrameworkStatsLog}.
+ */
+public class MetricUtilities {
+
+ private static final String TAG = "MetricUtilities";
+
+ // Metrics constants
+ protected static final int METRICS_API_NAME_UNKNOWN =
+ CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+ protected static final int METRICS_API_NAME_GET_CREDENTIAL =
+ CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+ protected static final int METRICS_API_NAME_CREATE_CREDENTIAL =
+ CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+ protected static final int METRICS_API_NAME_CLEAR_CREDENTIAL =
+ CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+ // TODO add isEnabled
+ protected static final int METRICS_API_STATUS_SUCCESS =
+ CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS;
+ protected static final int METRICS_API_STATUS_FAILURE =
+ CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE;
+ protected static final int METRICS_API_STATUS_CLIENT_CANCEL =
+ CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED;
+ protected static final int METRICS_API_STATUS_USER_CANCEL =
+ CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED;
+ protected static final int METRICS_PROVIDER_STATUS_FINAL_FAILURE =
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
+ protected static final int METRICS_PROVIDER_STATUS_QUERY_FAILURE =
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
+ protected static final int METRICS_PROVIDER_STATUS_FINAL_SUCCESS =
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
+ protected static final int METRICS_PROVIDER_STATUS_QUERY_SUCCESS =
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
+ protected static final int METRICS_PROVIDER_STATUS_UNKNOWN =
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+
+
+ /**
+ * This retrieves the uid of any package name, given a context and a component name for the
+ * package. By default, if the desired package uid cannot be found, it will fall back to a
+ * bogus uid.
+ * @return the uid of a given package
+ */
+ protected static int getPackageUid(Context context, ComponentName componentName) {
+ int sessUid = -1;
+ try {
+ // Only for T and above, which is fine for our use case
+ sessUid = context.getPackageManager().getApplicationInfo(
+ componentName.getPackageName(),
+ PackageManager.ApplicationInfoFlags.of(0)).uid;
+ } catch (Throwable t) {
+ Log.i(TAG, "Couldn't find required uid");
+ }
+ return sessUid;
+ }
+
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index b20f0cd..ce9fca7 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -33,7 +33,7 @@
*
* @hide
*/
-public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
Void>
implements
RemoteCredentialService.ProviderCallbacks<Void> {
@@ -120,6 +120,7 @@
protected void invokeSession() {
if (mRemoteCredentialService != null) {
mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
+ mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index b4c4233..3245c91 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.credentials.CreateCredentialException;
@@ -56,14 +57,11 @@
// Key to be used as an entry key for a remote entry
private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
- @NonNull
- private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
- /** The complete request to be used in the second round. */
private final CreateCredentialRequest mCompleteRequest;
private CreateCredentialException mProviderException;
- @Nullable protected Pair<String, CreateEntry> mUiRemoteEntry;
+ private final ProviderResponseDataHandler mProviderResponseDataHandler;
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderCreateSession createNewSession(
@@ -88,7 +86,8 @@
createRequestSession.mClientAppInfo,
createRequestSession
.mClientRequest.alwaysSendAppInfoToProvider()),
- providerCreateRequest
+ providerCreateRequest,
+ createRequestSession.mHybridService
);
}
Log.i(TAG, "Unable to create provider session");
@@ -131,23 +130,20 @@
@UserIdInt int userId,
@NonNull RemoteCredentialService remoteCredentialService,
@NonNull BeginCreateCredentialRequest beginCreateRequest,
- @NonNull CreateCredentialRequest completeCreateRequest) {
+ @NonNull CreateCredentialRequest completeCreateRequest,
+ String hybridService) {
super(context, info, beginCreateRequest, callbacks, userId,
remoteCredentialService);
mCompleteRequest = completeCreateRequest;
setStatus(Status.PENDING);
- }
-
- /** Returns the save entry maintained in state by this provider session. */
- public CreateEntry getUiSaveEntry(String entryId) {
- return mUiSaveEntries.get(entryId);
+ mProviderResponseDataHandler = new ProviderResponseDataHandler(hybridService);
}
@Override
public void onProviderResponseSuccess(
@Nullable BeginCreateCredentialResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
- onUpdateResponse(response);
+ onSetInitialRemoteResponse(response);
}
/** Called when the provider response resulted in a failure. */
@@ -171,21 +167,18 @@
}
}
- private void onUpdateResponse(BeginCreateCredentialResponse response) {
- Log.i(TAG, "updateResponse with save entries");
+ private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
+ Log.i(TAG, "onSetInitialRemoteResponse with save entries");
mProviderResponse = response;
- if (isEmptyResponse(response)) {
+ mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
+ response.getRemoteCreateEntry());
+ if (mProviderResponseDataHandler.isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
} else {
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
}
}
- private boolean isEmptyResponse(BeginCreateCredentialResponse response) {
- return (response.getCreateEntries() == null || response.getCreateEntries().isEmpty())
- && response.getRemoteCreateEntry() == null;
- }
-
@Override
@Nullable protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
@@ -194,53 +187,37 @@
Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
return null;
}
- final BeginCreateCredentialResponse response = getProviderResponse();
- if (response == null) {
- Log.i(TAG, "In prepareUiData response null");
- throw new IllegalStateException("Response must be in completion mode");
- }
- if (response.getCreateEntries() != null) {
+
+ if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
Log.i(TAG, "In prepareUiData save entries not null");
- return prepareUiProviderData(
- prepareUiSaveEntries(response.getCreateEntries()),
- prepareUiRemoteEntry(response.getRemoteCreateEntry()));
+ return mProviderResponseDataHandler.toCreateCredentialProviderData();
}
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) {
switch (entryType) {
case SAVE_ENTRY_KEY:
- if (mUiSaveEntries.containsKey(entryKey)) {
- onSaveEntrySelected(providerPendingIntentResponse);
- } else {
+ if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
Log.i(TAG, "Unexpected save entry key");
invokeCallbackOnInternalInvalidState();
+ return;
}
+ onCreateEntrySelected(providerPendingIntentResponse);
break;
case REMOTE_ENTRY_KEY:
- if (mUiRemoteEntry.first.equals(entryKey)) {
- onRemoteEntrySelected(providerPendingIntentResponse);
- } else {
+ if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
Log.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
+ return;
}
+ onRemoteEntrySelected(providerPendingIntentResponse);
break;
default:
Log.i(TAG, "Unsupported entry type selected");
+ invokeCallbackOnInternalInvalidState();
}
}
@@ -248,24 +225,10 @@
protected void invokeSession() {
if (mRemoteCredentialService != null) {
mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
+ mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
- private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
- Log.i(TAG, "in populateUiSaveEntries");
- List<Entry> uiSaveEntries = new ArrayList<>();
-
- // Populate the save entries
- for (CreateEntry createEntry : saveEntries) {
- 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(),
- setUpFillInIntent()));
- }
- return uiSaveEntries;
- }
-
private Intent setUpFillInIntent() {
Intent intent = new Intent();
intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
@@ -273,16 +236,7 @@
return intent;
}
- private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
- Entry remoteEntry) {
- return new CreateCredentialProviderData.Builder(
- mComponentName.flattenToString())
- .setSaveEntries(saveEntries)
- .setRemoteEntry(remoteEntry)
- .build();
- }
-
- private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
+ private void onCreateEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
CreateCredentialException exception = maybeGetPendingIntentException(
pendingIntentResponse);
if (exception != null) {
@@ -296,7 +250,6 @@
pendingIntentResponse.getResultData());
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
- return;
} else {
Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ "intent response");
@@ -304,6 +257,12 @@
}
}
+ private void onRemoteEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
+ // Response from remote entry should be dealt with similar to a response from a
+ // create entry
+ onCreateEntrySelected(pendingIntentResponse);
+ }
+
@Nullable
private CreateCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
@@ -335,4 +294,91 @@
CreateCredentialException.TYPE_UNKNOWN,
null);
}
+
+ private class ProviderResponseDataHandler {
+ private final ComponentName mExpectedRemoteEntryProviderService;
+
+ @NonNull
+ private final Map<String, Pair<CreateEntry, Entry>> mUiCreateEntries = new HashMap<>();
+
+ @Nullable private Pair<String, Pair<CreateEntry, Entry>> mUiRemoteEntry = null;
+
+ ProviderResponseDataHandler(String hybridService) {
+ mExpectedRemoteEntryProviderService = ComponentName.unflattenFromString(hybridService);
+ }
+
+ public void addResponseContent(List<CreateEntry> createEntries,
+ CreateEntry remoteEntry) {
+ createEntries.forEach(this::addCreateEntry);
+ setRemoteEntry(remoteEntry);
+ }
+ public void addCreateEntry(CreateEntry createEntry) {
+ String id = generateUniqueId();
+ Entry entry = new Entry(SAVE_ENTRY_KEY,
+ id, createEntry.getSlice(), setUpFillInIntent());
+ mUiCreateEntries.put(id, new Pair<>(createEntry, entry));
+ }
+
+ public void setRemoteEntry(@Nullable CreateEntry remoteEntry) {
+ if (remoteEntry == null) {
+ mUiRemoteEntry = null;
+ return;
+ }
+ if (!mComponentName.equals(mExpectedRemoteEntryProviderService)) {
+ Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ + "configured by the OEM.");
+ return;
+ }
+ String id = generateUniqueId();
+ Entry entry = new Entry(REMOTE_ENTRY_KEY,
+ id, remoteEntry.getSlice(), setUpFillInIntent());
+ mUiRemoteEntry = new Pair<>(id, new Pair<>(remoteEntry, entry));
+ }
+
+ public CreateCredentialProviderData toCreateCredentialProviderData() {
+ return new CreateCredentialProviderData.Builder(
+ mComponentName.flattenToString())
+ .setSaveEntries(prepareUiCreateEntries())
+ .setRemoteEntry(prepareRemoteEntry())
+ .build();
+ }
+
+ private List<Entry> prepareUiCreateEntries() {
+ List<Entry> createEntries = new ArrayList<>();
+ for (String key : mUiCreateEntries.keySet()) {
+ createEntries.add(mUiCreateEntries.get(key).second);
+ }
+ return createEntries;
+ }
+
+ private Entry prepareRemoteEntry() {
+ if (mUiRemoteEntry == null || mUiRemoteEntry.first == null
+ || mUiRemoteEntry.second == null) {
+ return null;
+ }
+ return mUiRemoteEntry.second.second;
+ }
+
+ private boolean isEmptyResponse() {
+ return mUiCreateEntries.isEmpty() && mUiRemoteEntry == null;
+ }
+ @Nullable
+ public CreateEntry getRemoteEntry(String entryKey) {
+ return mUiRemoteEntry == null || mUiRemoteEntry
+ .first == null || !mUiRemoteEntry.first.equals(entryKey)
+ || mUiRemoteEntry.second == null
+ ? null : mUiRemoteEntry.second.first;
+ }
+
+ @Nullable
+ public CreateEntry getCreateEntry(String entryKey) {
+ return mUiCreateEntries.get(entryKey) == null
+ ? null : mUiCreateEntries.get(entryKey).first;
+ }
+
+ public boolean isEmptyResponse(BeginCreateCredentialResponse response) {
+ return (response.getCreateEntries() == null || response.getCreateEntries().isEmpty())
+ && response.getRemoteCreateEntry() == null;
+ }
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index c2a57a8..12074c7 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialOption;
@@ -45,7 +46,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
+import java.util.Optional;
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
@@ -69,14 +70,7 @@
@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;
@@ -84,6 +78,8 @@
private GetCredentialException mProviderException;
+ private final ProviderResponseDataHandler mProviderResponseDataHandler;
+
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderGetSession createNewSession(
Context context,
@@ -109,7 +105,8 @@
beginGetOptionToCredentialOptionMap),
filteredRequest,
getRequestSession.mClientAppInfo,
- beginGetOptionToCredentialOptionMap
+ beginGetOptionToCredentialOptionMap,
+ getRequestSession.mHybridService
);
}
Log.i(TAG, "Unable to create provider session");
@@ -137,7 +134,7 @@
}
@Nullable
- protected static android.credentials.GetCredentialRequest filterOptions(
+ private static android.credentials.GetCredentialRequest filterOptions(
List<String> providerCapabilities,
android.credentials.GetCredentialRequest clientRequest
) {
@@ -169,19 +166,22 @@
BeginGetCredentialRequest beginGetRequest,
android.credentials.GetCredentialRequest completeGetRequest,
CallingAppInfo callingAppInfo,
- Map<String, CredentialOption> beginGetOptionToCredentialOptionMap) {
+ Map<String, CredentialOption> beginGetOptionToCredentialOptionMap,
+ String hybridService) {
super(context, info, beginGetRequest, callbacks, userId, remoteCredentialService);
mCompleteRequest = completeGetRequest;
mCallingAppInfo = callingAppInfo;
setStatus(Status.PENDING);
mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
+ mProviderResponseDataHandler = new ProviderResponseDataHandler(
+ ComponentName.unflattenFromString(hybridService));
}
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
- onUpdateResponse(response);
+ onSetInitialRemoteResponse(response);
}
/** Called when the provider response resulted in a failure. */
@@ -207,18 +207,20 @@
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
+ Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
- CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
+ CredentialEntry credentialEntry = mProviderResponseDataHandler
+ .getCredentialEntry(entryKey);
if (credentialEntry == null) {
Log.i(TAG, "Unexpected credential entry key");
invokeCallbackOnInternalInvalidState();
return;
}
- onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
+ onCredentialEntrySelected(providerPendingIntentResponse);
break;
case ACTION_ENTRY_KEY:
- Action actionEntry = mUiActionsEntries.get(entryKey);
+ Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
if (actionEntry == null) {
Log.i(TAG, "Unexpected action entry key");
invokeCallbackOnInternalInvalidState();
@@ -227,16 +229,29 @@
onActionEntrySelected(providerPendingIntentResponse);
break;
case AUTHENTICATION_ACTION_ENTRY_KEY:
- Action authenticationEntry = mUiAuthenticationEntries.get(entryKey);
+ Action authenticationEntry = mProviderResponseDataHandler
+ .getAuthenticationAction(entryKey);
if (authenticationEntry == null) {
Log.i(TAG, "Unexpected authenticationEntry key");
invokeCallbackOnInternalInvalidState();
return;
}
- onAuthenticationEntrySelected(providerPendingIntentResponse);
+ boolean additionalContentReceived =
+ onAuthenticationEntrySelected(providerPendingIntentResponse);
+ if (additionalContentReceived) {
+ Log.i(TAG, "Additional content received - removing authentication entry");
+ mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
+ } else {
+ Log.i(TAG, "Additional content not received");
+ mProviderResponseDataHandler
+ .updateAuthEntryWithNoCredentialsReceived(entryKey);
+ }
+ if (!mProviderResponseDataHandler.isEmptyResponse()) {
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ }
break;
case REMOTE_ENTRY_KEY:
- if (mUiRemoteEntry.first.equals(entryKey)) {
+ if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
Log.i(TAG, "Unexpected remote entry key");
@@ -245,6 +260,7 @@
break;
default:
Log.i(TAG, "Unsupported entry type selected");
+ invokeCallbackOnInternalInvalidState();
}
}
@@ -252,6 +268,7 @@
protected void invokeSession() {
if (mRemoteCredentialService != null) {
mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
+ mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
@@ -263,61 +280,11 @@
+ mComponentName.flattenToString());
return null;
}
- if (mProviderResponse == null) {
- Log.i(TAG, "In prepareUiData response null");
- throw new IllegalStateException("Response must be in completion mode");
+ if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
+ return mProviderResponseDataHandler.toGetCredentialProviderData();
}
- return prepareUiProviderData(prepareUiActionEntries(
- mProviderResponse.getActions()),
- prepareUiCredentialEntries(mProviderResponse.getCredentialEntries()),
- prepareUiAuthenticationEntries(mProviderResponse.getAuthenticationActions()),
- prepareUiRemoteEntry(mProviderResponse.getRemoteCredentialEntry()));
- }
-
- private Entry prepareUiRemoteEntry(CredentialEntry remoteCredentialEntry) {
- if (remoteCredentialEntry == null) {
- return null;
- }
- String entryId = generateUniqueId();
- Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice(),
- setUpFillInIntent(remoteCredentialEntry.getType()));
- mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
- return remoteEntry;
- }
-
- private List<AuthenticationEntry> prepareUiAuthenticationEntries(
- @NonNull List<Action> authenticationEntries) {
- List<AuthenticationEntry> authenticationUiEntries = new ArrayList<>();
-
- // TODO: properly construct entries when they should have the unlocked status.
- for (Action authenticationAction : authenticationEntries) {
- String entryId = generateUniqueId();
- mUiAuthenticationEntries.put(entryId, authenticationAction);
- authenticationUiEntries.add(new AuthenticationEntry(
- AUTHENTICATION_ACTION_ENTRY_KEY, entryId,
- authenticationAction.getSlice(),
- AuthenticationEntry.STATUS_LOCKED,
- setUpFillInIntentForAuthentication()));
- }
- return authenticationUiEntries;
- }
-
- private List<Entry> prepareUiCredentialEntries(@NonNull
- List<CredentialEntry> credentialEntries) {
- Log.i(TAG, "in prepareUiProviderDataWithCredentials");
- List<Entry> credentialUiEntries = new ArrayList<>();
-
- // Populate the credential entries
- for (CredentialEntry credentialEntry : credentialEntries) {
- 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,
- credentialEntry.getSlice(),
- /*fillInIntent=*/setUpFillInIntent(credentialEntry
- .getBeginGetCredentialOption().getId())));
- }
- return credentialUiEntries;
+ Log.i(TAG, "In prepareUiData response null");
+ return null;
}
private Intent setUpFillInIntent(@NonNull String id) {
@@ -334,68 +301,47 @@
mCallingAppInfo, mBeginGetOptionToCredentialOptionMap.get(id)));
}
- private Intent setUpFillInIntentForAuthentication() {
+ private Intent setUpFillInIntentWithQueryRequest() {
Intent intent = new Intent();
- intent.putExtra(
- CredentialProviderService
- .EXTRA_BEGIN_GET_CREDENTIAL_REQUEST,
+ intent.putExtra(CredentialProviderService.EXTRA_BEGIN_GET_CREDENTIAL_REQUEST,
mProviderRequest);
return intent;
}
- private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
- List<Entry> actionEntries = new ArrayList<>();
- for (Action action : actions) {
- String entryId = UUID.randomUUID().toString();
- mUiActionsEntries.put(entryId, action);
- // TODO : Remove conversion of string to int after change in Entry class
- actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice()));
- }
- return actionEntries;
- }
-
- private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
- List<Entry> credentialEntries, List<AuthenticationEntry> authenticationActionEntries,
- Entry remoteEntry) {
- return new GetCredentialProviderData.Builder(
- mComponentName.flattenToString()).setActionChips(actionEntries)
- .setCredentialEntries(credentialEntries)
- .setAuthenticationEntries(authenticationActionEntries)
- .setRemoteEntry(remoteEntry)
- .build();
- }
-
- private void onCredentialEntrySelected(CredentialEntry credentialEntry,
+ private void onRemoteEntrySelected(
ProviderPendingIntentResponse providerPendingIntentResponse) {
- if (providerPendingIntentResponse != null) {
- // Check if pending intent has an error
- GetCredentialException exception = maybeGetPendingIntentException(
- providerPendingIntentResponse);
- if (exception != null) {
- invokeCallbackWithError(exception.getType(),
- exception.getMessage());
- return;
- }
+ onCredentialEntrySelected(providerPendingIntentResponse);
+ }
- // Check if pending intent has a credential
- GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
- .extractGetCredentialResponse(
- providerPendingIntentResponse.getResultData());
- if (getCredentialResponse != null) {
- mCallbacks.onFinalResponseReceived(mComponentName,
- getCredentialResponse);
- return;
- }
-
- Log.i(TAG, "Pending intent response contains no credential, or error");
+ private void onCredentialEntrySelected(
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ if (providerPendingIntentResponse == null) {
invokeCallbackOnInternalInvalidState();
+ return;
}
- Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+ // Check if pending intent has an error
+ GetCredentialException exception = maybeGetPendingIntentException(
+ providerPendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(exception.getType(), exception.getMessage());
+ return;
+ }
+
+ // Check if pending intent has a credential response
+ GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
+ .extractGetCredentialResponse(
+ providerPendingIntentResponse.getResultData());
+ if (getCredentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName,
+ getCredentialResponse);
+ return;
+ }
+ Log.i(TAG, "Pending intent response contains no credential, or error");
invokeCallbackOnInternalInvalidState();
}
@Nullable
- protected GetCredentialException maybeGetPendingIntentException(
+ private GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
Log.i(TAG, "pendingIntentResponse is null");
@@ -416,13 +362,19 @@
return null;
}
- private void onAuthenticationEntrySelected(
+ /**
+ * Returns true if either an exception or a response is retrieved from the result.
+ * Returns false if the response is not set at all, or set to null, or empty.
+ */
+ private boolean onAuthenticationEntrySelected(
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
- //TODO: Other provider intent statuses
+ Log.i(TAG, "onAuthenticationEntrySelected");
+ // Authentication entry is expected to have a BeginGetCredentialResponse instance. If it
+ // does not have it, we remove the authentication entry and do not add any more content.
if (providerPendingIntentResponse == null) {
Log.i(TAG, "providerPendingIntentResponse is null");
- onUpdateEmptyResponse();
- return;
+ // Nothing received. This is equivalent to no content received.
+ return false;
}
GetCredentialException exception = maybeGetPendingIntentException(
@@ -430,51 +382,69 @@
if (exception != null) {
invokeCallbackWithError(exception.getType(),
exception.getMessage());
- return;
+ // Additional content received is in the form of an exception which ends the flow.
+ return true;
}
-
- // Check if pending intent has the content
- BeginGetCredentialResponse content = PendingIntentResultHandler
+ // Check if pending intent has the response. If yes, remove this auth entry and
+ // replace it with the response content received.
+ BeginGetCredentialResponse response = PendingIntentResultHandler
.extractResponseContent(providerPendingIntentResponse
.getResultData());
- if (content != null) {
- onUpdateResponse(content);
+ if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) {
+ addToInitialRemoteResponse(response);
+ // Additional content received is in the form of new response content.
+ return true;
+ }
+ // No response or exception found.
+ return false;
+ }
+
+ private void addToInitialRemoteResponse(BeginGetCredentialResponse content) {
+ if (content == null) {
+ return;
+ }
+ mProviderResponseDataHandler.addResponseContent(
+ content.getCredentialEntries(),
+ content.getActions(),
+ content.getAuthenticationActions(),
+ content.getRemoteCredentialEntry()
+ );
+ }
+
+ /** Returns true if either an exception or a response is found. */
+ private void onActionEntrySelected(ProviderPendingIntentResponse
+ providerPendingIntentResponse) {
+ // Action entry is expected to either contain the final GetCredentialResponse, or it is
+ // also acceptable if it does not contain anything. In the second case, we re-show this
+ // action on the UI.
+ if (providerPendingIntentResponse == null) {
+ Log.i(TAG, "providerPendingIntentResponse is null");
return;
}
- Log.i(TAG, "No error or respond found in pending intent response");
- onUpdateEmptyResponse();
- }
-
- private void onActionEntrySelected(ProviderPendingIntentResponse
- providerPendingIntentResponse) {
- //TODO: Implement if any result expected after an action
+ GetCredentialException exception = maybeGetPendingIntentException(
+ providerPendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(exception.getType(), exception.getMessage());
+ }
+ GetCredentialResponse response = PendingIntentResultHandler
+ .extractGetCredentialResponse(
+ providerPendingIntentResponse.getResultData());
+ if (response != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, response);
+ }
}
/** Updates the response being maintained in state by this provider session. */
- private void onUpdateResponse(BeginGetCredentialResponse response) {
+ private void onSetInitialRemoteResponse(BeginGetCredentialResponse response) {
mProviderResponse = response;
- if (isEmptyResponse(response)) {
+ addToInitialRemoteResponse(response);
+ if (mProviderResponseDataHandler.isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
- } else {
- updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ return;
}
- }
-
- private boolean isEmptyResponse(BeginGetCredentialResponse response) {
- if ((response.getCredentialEntries() == null || response.getCredentialEntries().isEmpty())
- && (response.getAuthenticationActions() == null || response
- .getAuthenticationActions().isEmpty())
- && (response.getActions() == null || response.getActions().isEmpty())
- && response.getRemoteCredentialEntry() == null) {
- return true;
- }
- return false;
- }
-
- private void onUpdateEmptyResponse() {
- updateStatusAndInvokeCallback(Status.NO_CREDENTIALS);
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
/**
@@ -485,4 +455,201 @@
mCallbacks.onFinalErrorReceived(mComponentName,
GetCredentialException.TYPE_UNKNOWN, null);
}
+
+ private class ProviderResponseDataHandler {
+ private final ComponentName mExpectedRemoteEntryProviderService;
+ @NonNull
+ private final Map<String, Pair<CredentialEntry, Entry>> mUiCredentialEntries =
+ new HashMap<>();
+ @NonNull
+ private final Map<String, Pair<Action, Entry>> mUiActionsEntries = new HashMap<>();
+ @Nullable
+ private final Map<String, Pair<Action, AuthenticationEntry>> mUiAuthenticationEntries =
+ new HashMap<>();
+
+ @Nullable private Pair<String, Pair<CredentialEntry, Entry>> mUiRemoteEntry = null;
+
+ ProviderResponseDataHandler(ComponentName expectedRemoteEntryProviderService) {
+ mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
+ }
+
+ public void addResponseContent(List<CredentialEntry> credentialEntries,
+ List<Action> actions, List<Action> authenticationActions,
+ CredentialEntry remoteEntry) {
+ credentialEntries.forEach(this::addCredentialEntry);
+ actions.forEach(this::addAction);
+ authenticationActions.forEach(
+ authenticationAction -> addAuthenticationAction(authenticationAction,
+ AuthenticationEntry.STATUS_LOCKED));
+ setRemoteEntry(remoteEntry);
+ }
+ public void addCredentialEntry(CredentialEntry credentialEntry) {
+ String id = generateUniqueId();
+ Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
+ id, credentialEntry.getSlice(),
+ setUpFillInIntent(credentialEntry
+ .getBeginGetCredentialOption().getId()));
+ mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
+ }
+
+ public void addAction(Action action) {
+ String id = generateUniqueId();
+ Entry entry = new Entry(ACTION_ENTRY_KEY,
+ id, action.getSlice(),
+ setUpFillInIntentWithQueryRequest());
+ mUiActionsEntries.put(id, new Pair<>(action, entry));
+ }
+
+ public void addAuthenticationAction(Action authenticationAction,
+ @AuthenticationEntry.Status int status) {
+ Log.i(TAG, "In addAuthenticationAction");
+ String id = generateUniqueId();
+ Log.i(TAG, "In addAuthenticationAction, id : " + id);
+ AuthenticationEntry entry = new AuthenticationEntry(
+ AUTHENTICATION_ACTION_ENTRY_KEY,
+ id, authenticationAction.getSlice(),
+ status,
+ setUpFillInIntentWithQueryRequest());
+ mUiAuthenticationEntries.put(id, new Pair<>(authenticationAction, entry));
+ }
+
+ public void removeAuthenticationAction(String id) {
+ mUiAuthenticationEntries.remove(id);
+ }
+
+ public void setRemoteEntry(@Nullable CredentialEntry remoteEntry) {
+ if (remoteEntry == null) {
+ return;
+ }
+ if (!mComponentName.equals(mExpectedRemoteEntryProviderService)) {
+ Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ + "configured by the OEM.");
+ return;
+ }
+ String id = generateUniqueId();
+ Entry entry = new Entry(REMOTE_ENTRY_KEY,
+ id, remoteEntry.getSlice(), setUpFillInIntent(
+ remoteEntry.getBeginGetCredentialOption().getId()));
+ mUiRemoteEntry = new Pair<>(generateUniqueId(), new Pair<>(remoteEntry, entry));
+ }
+
+ public GetCredentialProviderData toGetCredentialProviderData() {
+ return new GetCredentialProviderData.Builder(
+ mComponentName.flattenToString()).setActionChips(prepareActionEntries())
+ .setCredentialEntries(prepareCredentialEntries())
+ .setAuthenticationEntries(prepareAuthenticationEntries())
+ .setRemoteEntry(prepareRemoteEntry())
+ .build();
+ }
+
+ private List<Entry> prepareActionEntries() {
+ List<Entry> actionEntries = new ArrayList<>();
+ for (String key : mUiActionsEntries.keySet()) {
+ actionEntries.add(mUiActionsEntries.get(key).second);
+ }
+ return actionEntries;
+ }
+
+ private List<AuthenticationEntry> prepareAuthenticationEntries() {
+ List<AuthenticationEntry> authEntries = new ArrayList<>();
+ for (String key : mUiAuthenticationEntries.keySet()) {
+ authEntries.add(mUiAuthenticationEntries.get(key).second);
+ }
+ return authEntries;
+ }
+
+ private List<Entry> prepareCredentialEntries() {
+ List<Entry> credEntries = new ArrayList<>();
+ for (String key : mUiCredentialEntries.keySet()) {
+ credEntries.add(mUiCredentialEntries.get(key).second);
+ }
+ return credEntries;
+ }
+
+
+ private Entry prepareRemoteEntry() {
+ if (mUiRemoteEntry == null || mUiRemoteEntry.first == null
+ || mUiRemoteEntry.second == null) {
+ return null;
+ }
+ return mUiRemoteEntry.second.second;
+ }
+
+ private boolean isEmptyResponse() {
+ return mUiCredentialEntries.isEmpty() && mUiActionsEntries.isEmpty()
+ && mUiAuthenticationEntries.isEmpty() && mUiRemoteEntry == null;
+ }
+
+ private boolean isEmptyResponse(BeginGetCredentialResponse response) {
+ return response.getCredentialEntries().isEmpty() && response.getActions().isEmpty()
+ && response.getAuthenticationActions().isEmpty()
+ && response.getRemoteCredentialEntry() == null;
+ }
+
+ @Nullable
+ public Action getAuthenticationAction(String entryKey) {
+ return mUiAuthenticationEntries.get(entryKey) == null ? null :
+ mUiAuthenticationEntries.get(entryKey).first;
+ }
+
+ @Nullable
+ public Action getActionEntry(String entryKey) {
+ return mUiActionsEntries.get(entryKey) == null
+ ? null : mUiActionsEntries.get(entryKey).first;
+ }
+
+ @Nullable
+ public CredentialEntry getRemoteEntry(String entryKey) {
+ return mUiRemoteEntry.first.equals(entryKey) && mUiRemoteEntry.second != null
+ ? mUiRemoteEntry.second.first : null;
+ }
+
+ @Nullable
+ public CredentialEntry getCredentialEntry(String entryKey) {
+ return mUiCredentialEntries.get(entryKey) == null
+ ? null : mUiCredentialEntries.get(entryKey).first;
+ }
+
+ public void updateAuthEntryWithNoCredentialsReceived(String entryKey) {
+ updatePreviousMostRecentAuthEntry();
+ updateMostRecentAuthEntry(entryKey);
+ }
+
+ private void updateMostRecentAuthEntry(String entryKey) {
+ AuthenticationEntry previousAuthenticationEntry =
+ mUiAuthenticationEntries.get(entryKey).second;
+ Action previousAuthenticationAction = mUiAuthenticationEntries.get(entryKey).first;
+ mUiAuthenticationEntries.put(entryKey, new Pair<>(
+ previousAuthenticationAction,
+ copyAuthEntryAndChangeStatus(
+ previousAuthenticationEntry,
+ AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)));
+ }
+
+ private void updatePreviousMostRecentAuthEntry() {
+ Optional<Map.Entry<String, Pair<Action, AuthenticationEntry>>>
+ previousMostRecentAuthEntry = mUiAuthenticationEntries
+ .entrySet().stream().filter(e -> e.getValue().second.getStatus()
+ == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
+ .findFirst();
+ if (previousMostRecentAuthEntry.isEmpty()) {
+ Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
+ return;
+ }
+ String id = previousMostRecentAuthEntry.get().getKey();
+ mUiAuthenticationEntries.remove(id);
+ mUiAuthenticationEntries.put(id, new Pair<>(
+ previousMostRecentAuthEntry.get().getValue().first,
+ copyAuthEntryAndChangeStatus(
+ previousMostRecentAuthEntry.get().getValue().second,
+ AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT)));
+ }
+
+ private AuthenticationEntry copyAuthEntryAndChangeStatus(
+ AuthenticationEntry from, Integer toStatus) {
+ return new AuthenticationEntry(AUTHENTICATION_ACTION_ENTRY_KEY, from.getSubkey(),
+ from.getSlice(), toStatus,
+ from.getFrameworkExtrasIntent());
+ }
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index dd9fcb6..5af8080 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -235,6 +235,7 @@
-> filterResult.mCredentialEntries.stream())
.collect(Collectors.toList());
setStatus(Status.CREDENTIALS_RECEIVED);
+ // TODO(use metric later)
}
@Nullable
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 1aec934..2f9d578 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -16,6 +16,9 @@
package com.android.server.credentials;
+import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_FAILURE;
+import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_SUCCESS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -28,6 +31,8 @@
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
+import com.android.server.credentials.metrics.CandidateProviderMetric;
+
import java.util.UUID;
/**
@@ -52,16 +57,16 @@
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
@NonNull protected Boolean mProviderResponseSet = false;
-
-
+ // Specific candidate provider metric for the provider this session handles
+ @Nullable protected CandidateProviderMetric mCandidateProviderMetric;
+ @NonNull private int mProviderSessionUid;
/**
* Returns true if the given status reflects that the provider state is ready to be shown
* on the credMan UI.
*/
public static boolean isUiInvokingStatus(Status status) {
- return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
- || status == Status.REQUIRES_AUTHENTICATION;
+ return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
}
/**
@@ -119,6 +124,8 @@
mUserId = userId;
mComponentName = info.getServiceInfo().getComponentName();
mRemoteCredentialService = remoteCredentialService;
+ mCandidateProviderMetric = new CandidateProviderMetric();
+ mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
}
/** Provider status at various states of the request session. */
@@ -185,12 +192,19 @@
/** Updates the status .*/
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
setStatus(status);
+ updateCandidateMetric(status);
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
- protected void onRemoteEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse) {
- //TODO: Implement
+ private void updateCandidateMetric(Status status) {
+ mCandidateProviderMetric.setCandidateUid(mProviderSessionUid);
+ mCandidateProviderMetric
+ .setQueryFinishTimeNanoseconds(System.nanoTime());
+ if (isTerminatingStatus(status)) {
+ mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_FAILURE);
+ } else if (isCompletionStatus(status)) {
+ mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_SUCCESS);
+ }
}
/** Get the request to be sent to the provider. */
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 2dea8bd..702261e 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,6 +24,7 @@
import android.credentials.ClearCredentialStateException;
import android.credentials.CreateCredentialException;
import android.credentials.GetCredentialException;
+import android.os.Binder;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
@@ -55,7 +56,7 @@
*
* @hide
*/
-public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService>{
+public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService> {
private static final String TAG = "RemoteCredentialService";
/** Timeout for a single request. */
@@ -69,13 +70,16 @@
/**
* Callbacks to be invoked when the provider remote service responds with a
* success or failure.
+ *
* @param <T> the type of response expected from the provider
*/
public interface ProviderCallbacks<T> {
/** Called when a successful response is received from the remote provider. */
void onProviderResponseSuccess(@Nullable T response);
+
/** Called when a failure response is received from the remote provider. */
void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e);
+
/** Called when the remote provider service dies. */
void onProviderServiceDied(RemoteCredentialService service);
}
@@ -95,7 +99,8 @@
}
/** Return the componentName of the service to be connected. */
- @NonNull public ComponentName getComponentName() {
+ @NonNull
+ public ComponentName getComponentName() {
return mComponentName;
}
@@ -104,9 +109,11 @@
unbind();
}
- /** Main entry point to be called for executing a getCredential call on the remote
+ /**
+ * Main entry point to be called for executing a getCredential call on the remote
* provider service.
- * @param request the request to be sent to the provider
+ *
+ * @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderGetSession} class that maintains provider state
*/
@@ -120,30 +127,36 @@
CompletableFuture<BeginGetCredentialResponse> connectThenExecute = postAsync(service -> {
CompletableFuture<BeginGetCredentialResponse> getCredentials =
new CompletableFuture<>();
- ICancellationSignal cancellationSignal =
- service.onBeginGetCredential(request,
- new IBeginGetCredentialCallback.Stub() {
- @Override
- public void onSuccess(BeginGetCredentialResponse response) {
- Log.i(TAG, "In onSuccess in RemoteCredentialService");
- getCredentials.complete(response);
- }
+ final long originalCallingUidToken = Binder.clearCallingIdentity();
+ try {
+ ICancellationSignal cancellationSignal =
+ service.onBeginGetCredential(request,
+ new IBeginGetCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(BeginGetCredentialResponse response) {
+ Log.i(TAG, "In onSuccess in RemoteCredentialService");
+ getCredentials.complete(response);
+ }
- @Override
- public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
- String errorMsg = message == null ? "" : String.valueOf(message);
- getCredentials.completeExceptionally(
- new GetCredentialException(errorType, errorMsg));
- }
- });
- CompletableFuture<BeginGetCredentialResponse> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellationSignal);
- } else {
- cancellationSink.set(cancellationSignal);
+ @Override
+ public void onFailure(String errorType, CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(
+ message);
+ getCredentials.completeExceptionally(
+ new GetCredentialException(errorType, errorMsg));
+ }
+ });
+ CompletableFuture<BeginGetCredentialResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
+ } else {
+ cancellationSink.set(cancellationSignal);
+ }
+ return getCredentials;
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingUidToken);
}
- return getCredentials;
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenExecute);
@@ -153,9 +166,11 @@
return cancellationSink.get();
}
- /** Main entry point to be called for executing a beginCreateCredential call on the remote
+ /**
+ * Main entry point to be called for executing a beginCreateCredential call on the remote
* provider service.
- * @param request the request to be sent to the provider
+ *
+ * @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderCreateSession} class that maintains provider state
*/
@@ -168,32 +183,39 @@
CompletableFuture<BeginCreateCredentialResponse> connectThenExecute =
postAsync(service -> {
- CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
- new CompletableFuture<>();
- ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
- request, new IBeginCreateCredentialCallback.Stub() {
- @Override
- public void onSuccess(BeginCreateCredentialResponse response) {
- Log.i(TAG, "In onSuccess onBeginCreateCredential "
- + "in RemoteCredentialService");
- createCredentialFuture.complete(response);
- }
+ CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
+ new CompletableFuture<>();
+ final long originalCallingUidToken = Binder.clearCallingIdentity();
+ try {
+ ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+ request, new IBeginCreateCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(BeginCreateCredentialResponse response) {
+ Log.i(TAG, "In onSuccess onBeginCreateCredential "
+ + "in RemoteCredentialService");
+ createCredentialFuture.complete(response);
+ }
- @Override
- public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
- String errorMsg = message == null ? "" : String.valueOf(message);
- createCredentialFuture.completeExceptionally(
- new CreateCredentialException(errorType, errorMsg));
- }});
- CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellationSignal);
- } else {
- cancellationSink.set(cancellationSignal);
- }
- return createCredentialFuture;
- }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+ @Override
+ public void onFailure(String errorType, CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(
+ message);
+ createCredentialFuture.completeExceptionally(
+ new CreateCredentialException(errorType, errorMsg));
+ }
+ });
+ CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
+ } else {
+ cancellationSink.set(cancellationSignal);
+ }
+ return createCredentialFuture;
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingUidToken);
+ }
+ }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
@@ -202,9 +224,11 @@
return cancellationSink.get();
}
- /** Main entry point to be called for executing a clearCredentialState call on the remote
+ /**
+ * Main entry point to be called for executing a clearCredentialState call on the remote
* provider service.
- * @param request the request to be sent to the provider
+ *
+ * @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderClearSession} class that maintains provider state
*/
@@ -218,30 +242,37 @@
postAsync(service -> {
CompletableFuture<Void> clearCredentialFuture =
new CompletableFuture<>();
- ICancellationSignal cancellationSignal = service.onClearCredentialState(
- request, new IClearCredentialStateCallback.Stub() {
- @Override
- public void onSuccess() {
- Log.i(TAG, "In onSuccess onClearCredentialState "
- + "in RemoteCredentialService");
- clearCredentialFuture.complete(null);
- }
+ final long originalCallingUidToken = Binder.clearCallingIdentity();
+ try {
+ ICancellationSignal cancellationSignal = service.onClearCredentialState(
+ request, new IClearCredentialStateCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ Log.i(TAG, "In onSuccess onClearCredentialState "
+ + "in RemoteCredentialService");
+ clearCredentialFuture.complete(null);
+ }
- @Override
- public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
- String errorMsg = message == null ? "" :
- String.valueOf(message);
- clearCredentialFuture.completeExceptionally(
- new ClearCredentialStateException(errorType, errorMsg));
- }});
- CompletableFuture<Void> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellationSignal);
- } else {
- cancellationSink.set(cancellationSignal);
+ @Override
+ public void onFailure(String errorType, CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" :
+ String.valueOf(message);
+ clearCredentialFuture.completeExceptionally(
+ new ClearCredentialStateException(errorType,
+ errorMsg));
+ }
+ });
+ CompletableFuture<Void> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
+ } else {
+ cancellationSink.set(cancellationSignal);
+ }
+ return clearCredentialFuture;
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingUidToken);
}
- return clearCredentialFuture;
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenExecute);
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index fdd0e81..8622665 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -16,15 +16,16 @@
package com.android.server.credentials;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS;
+import static com.android.server.credentials.MetricUtilities.METRICS_API_NAME_CLEAR_CREDENTIAL;
+import static com.android.server.credentials.MetricUtilities.METRICS_API_NAME_CREATE_CREDENTIAL;
+import static com.android.server.credentials.MetricUtilities.METRICS_API_NAME_GET_CREDENTIAL;
+import static com.android.server.credentials.MetricUtilities.METRICS_API_NAME_UNKNOWN;
+import static com.android.server.credentials.MetricUtilities.METRICS_API_STATUS_FAILURE;
+import static com.android.server.credentials.MetricUtilities.METRICS_API_STATUS_SUCCESS;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.Context;
import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
@@ -37,7 +38,10 @@
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
+import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.credentials.metrics.CandidateProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderMetric;
import java.util.ArrayList;
import java.util.HashMap;
@@ -50,20 +54,6 @@
abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback {
private static final String TAG = "RequestSession";
- // Metrics constants
- private static final int METRICS_API_NAME_UNKNOWN =
- CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
- private static final int METRICS_API_NAME_GET_CREDENTIAL =
- CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
- private static final int METRICS_API_NAME_CREATE_CREDENTIAL =
- CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
- private static final int METRICS_API_NAME_CLEAR_CREDENTIAL =
- CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
- private static final int METRICS_API_STATUS_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS;
- private static final int METRICS_API_STATUS_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE;
-
// TODO: Revise access levels of attributes
@NonNull
protected final T mClientRequest;
@@ -88,6 +78,9 @@
protected final CancellationSignal mCancellationSignal;
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
+ protected ChosenProviderMetric mChosenProviderMetric = new ChosenProviderMetric();
+ //TODO improve design to allow grouped metrics per request
+ protected final String mHybridService;
protected RequestSession(@NonNull Context context,
@UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback,
@@ -106,6 +99,8 @@
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
mUserId, this);
+ mHybridService = context.getResources().getString(
+ R.string.config_defaultCredentialManagerHybridService);
}
public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
@@ -165,7 +160,6 @@
}
return false;
}
-
// TODO: move these definitions to a separate logging focused class.
enum RequestType {
GET_CREDENTIALS,
@@ -186,11 +180,30 @@
}
}
- protected void logApiCalled(RequestType requestType, boolean isSuccessful) {
+ protected void logApiCalled(RequestType requestType, boolean isSuccessfulOverall) {
+ var providerSessions = mProviders.values();
+ int providerSize = providerSessions.size();
+ int[] candidateUidList = new int[providerSize];
+ int[] candidateQueryRoundTripTimeList = new int[providerSize];
+ int[] candidateStatusList = new int[providerSize];
+ int index = 0;
+ for (var session : providerSessions) {
+ CandidateProviderMetric metric = session.mCandidateProviderMetric;
+ candidateUidList[index] = metric.getCandidateUid();
+ candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMs();
+ candidateStatusList[index] = metric.getProviderQueryStatus();
+ index++;
+ }
FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
/* api_name */getApiNameFromRequestType(requestType), /* caller_uid */
mCallingUid, /* api_status */
- isSuccessful ? METRICS_API_STATUS_SUCCESS : METRICS_API_STATUS_FAILURE);
+ isSuccessfulOverall ? METRICS_API_STATUS_SUCCESS : METRICS_API_STATUS_FAILURE,
+ candidateUidList,
+ candidateQueryRoundTripTimeList,
+ candidateStatusList, mChosenProviderMetric.getChosenUid(),
+ mChosenProviderMetric.getEntireProviderLatencyMs(),
+ mChosenProviderMetric.getFinalPhaseLatencyMs(),
+ mChosenProviderMetric.getChosenProviderStatus());
}
protected boolean isSessionCancelled() {
@@ -239,4 +252,18 @@
}
}
}
+
+ /**
+ * Called by RequestSession's upon chosen metric determination.
+ * @param componentName the componentName to associate with a provider
+ */
+ protected void setChosenMetric(ComponentName componentName) {
+ CandidateProviderMetric metric = this.mProviders.get(componentName.flattenToString())
+ .mCandidateProviderMetric;
+ mChosenProviderMetric.setChosenUid(metric.getCandidateUid());
+ mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
+ mChosenProviderMetric.setQueryFinishTimeNanoseconds(
+ metric.getQueryFinishTimeNanoseconds());
+ mChosenProviderMetric.setStartTimeNanoseconds(metric.getStartTimeNanoseconds());
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
new file mode 100644
index 0000000..acfb4a4
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+/**
+ * The central candidate provider metric object that mimics our defined metric setup.
+ */
+public class CandidateProviderMetric {
+
+ private int mCandidateUid = -1;
+ private long mStartTimeNanoseconds = -1;
+ private long mQueryFinishTimeNanoseconds = -1;
+
+ private int mProviderQueryStatus = -1;
+
+ public CandidateProviderMetric(long startTime, long queryFinishTime, int providerQueryStatus,
+ int candidateUid) {
+ this.mStartTimeNanoseconds = startTime;
+ this.mQueryFinishTimeNanoseconds = queryFinishTime;
+ this.mProviderQueryStatus = providerQueryStatus;
+ this.mCandidateUid = candidateUid;
+ }
+
+ public CandidateProviderMetric(){}
+
+ public void setStartTimeNanoseconds(long startTimeNanoseconds) {
+ this.mStartTimeNanoseconds = startTimeNanoseconds;
+ }
+
+ public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
+ this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
+ }
+
+ public void setProviderQueryStatus(int providerQueryStatus) {
+ this.mProviderQueryStatus = providerQueryStatus;
+ }
+
+ public void setCandidateUid(int candidateUid) {
+ this.mCandidateUid = candidateUid;
+ }
+
+ public long getStartTimeNanoseconds() {
+ return this.mStartTimeNanoseconds;
+ }
+
+ public long getQueryFinishTimeNanoseconds() {
+ return this.mQueryFinishTimeNanoseconds;
+ }
+
+ public int getProviderQueryStatus() {
+ return this.mProviderQueryStatus;
+ }
+
+ public int getCandidateUid() {
+ return this.mCandidateUid;
+ }
+
+ /**
+ * Returns the latency in microseconds for the query phase.
+ */
+ public int getQueryLatencyMs() {
+ return (int) ((this.getQueryFinishTimeNanoseconds()
+ - this.getStartTimeNanoseconds()) / 1000);
+ }
+
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
new file mode 100644
index 0000000..c4d0b3c
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+/**
+ * The central chosen provider metric object that mimics our defined metric setup.
+ */
+public class ChosenProviderMetric {
+
+ private int mChosenUid = -1;
+ private long mStartTimeNanoseconds = -1;
+ private long mQueryFinishTimeNanoseconds = -1;
+ private long mFinalFinishTimeNanoseconds = -1;
+ private int mChosenProviderStatus = -1;
+
+ public ChosenProviderMetric() {}
+
+ public int getChosenUid() {
+ return mChosenUid;
+ }
+
+ public void setChosenUid(int chosenUid) {
+ mChosenUid = chosenUid;
+ }
+
+ public long getStartTimeNanoseconds() {
+ return mStartTimeNanoseconds;
+ }
+
+ public void setStartTimeNanoseconds(long startTimeNanoseconds) {
+ mStartTimeNanoseconds = startTimeNanoseconds;
+ }
+
+ public long getQueryFinishTimeNanoseconds() {
+ return mQueryFinishTimeNanoseconds;
+ }
+
+ public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
+ mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
+ }
+
+ public long getFinalFinishTimeNanoseconds() {
+ return mFinalFinishTimeNanoseconds;
+ }
+
+ public void setFinalFinishTimeNanoseconds(long finalFinishTimeNanoseconds) {
+ mFinalFinishTimeNanoseconds = finalFinishTimeNanoseconds;
+ }
+
+ public int getChosenProviderStatus() {
+ return mChosenProviderStatus;
+ }
+
+ public void setChosenProviderStatus(int chosenProviderStatus) {
+ mChosenProviderStatus = chosenProviderStatus;
+ }
+
+ /**
+ * Returns the full provider (invocation to response) latency in microseconds.
+ */
+ public int getEntireProviderLatencyMs() {
+ return (int) ((this.getFinalFinishTimeNanoseconds()
+ - this.getStartTimeNanoseconds()) / 1000);
+ }
+
+ // TODO get post click final phase and re-add the query phase time to metric
+
+ /**
+ * Returns the end of query to response phase latency in microseconds.
+ */
+ public int getFinalPhaseLatencyMs() {
+ return (int) ((this.getFinalFinishTimeNanoseconds()
+ - this.getQueryFinishTimeNanoseconds()) / 1000);
+ }
+
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index d7a2095..88b82d7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -176,7 +176,8 @@
private static final String ATTR_PACKAGE_POLICY_MODE = "package-policy-type";
private static final String TAG_CREDENTIAL_MANAGER_POLICY = "credential-manager-policy";
-
+ // If the ActiveAdmin is a permission-based admin, then info will be null because the
+ // permission-based admin is not mapped to a device administrator component.
DeviceAdminInfo info;
static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
@@ -378,9 +379,11 @@
void writeToXml(TypedXmlSerializer out)
throws IllegalArgumentException, IllegalStateException, IOException {
- out.startTag(null, TAG_POLICIES);
- info.writePoliciesToXml(out);
- out.endTag(null, TAG_POLICIES);
+ if (info != null) {
+ out.startTag(null, TAG_POLICIES);
+ info.writePoliciesToXml(out);
+ out.endTag(null, TAG_POLICIES);
+ }
if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
writeAttributeValueToXml(
out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
@@ -1188,14 +1191,16 @@
pw.print("testOnlyAdmin=");
pw.println(testOnlyAdmin);
- pw.println("policies:");
- ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
- if (pols != null) {
- pw.increaseIndent();
- for (int i = 0; i < pols.size(); i++) {
- pw.println(pols.get(i).tag);
+ if (info != null) {
+ pw.println("policies:");
+ ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
+ if (pols != null) {
+ pw.increaseIndent();
+ for (int i = 0; i < pols.size(); i++) {
+ pw.println(pols.get(i).tag);
+ }
+ pw.decreaseIndent();
}
- pw.decreaseIndent();
}
pw.print("passwordQuality=0x");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 8e430b3..a5b9d43 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -123,6 +123,23 @@
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
+ // Some DevicePolicyManager APIs can be called by (1) a DPC or (2) an app with permissions that
+ // isn't a DPC. For the latter, the caller won't have to provide a ComponentName and won't be
+ // mapped to an ActiveAdmin. This permission-based admin should be used to persist policies
+ // set by the permission-based caller. This admin should not be added to mAdminMap or mAdminList
+ // since a lot of methods in DPMS assume the ActiveAdmins here have a valid ComponentName.
+ // Instead, use variants of DPMS active admin getters to include the permission-based admin.
+ ActiveAdmin mPermissionBasedAdmin;
+
+ // Create or get the permission-based admin. The permission-based admin will not have a
+ // DeviceAdminInfo or ComponentName.
+ ActiveAdmin createOrGetPermissionBasedAdmin() {
+ if (mPermissionBasedAdmin == null) {
+ mPermissionBasedAdmin = new ActiveAdmin(/* info= */ null, /* parent= */ false);
+ }
+ return mPermissionBasedAdmin;
+ }
+
// TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
@@ -256,6 +273,12 @@
}
}
+ if (policyData.mPermissionBasedAdmin != null) {
+ out.startTag(null, "permission-based-admin");
+ policyData.mPermissionBasedAdmin.writeToXml(out);
+ out.endTag(null, "permission-based-admin");
+ }
+
if (policyData.mPasswordOwner >= 0) {
out.startTag(null, "password-owner");
out.attributeInt(null, "value", policyData.mPasswordOwner);
@@ -457,6 +480,7 @@
policy.mLockTaskPackages.clear();
policy.mAdminList.clear();
policy.mAdminMap.clear();
+ policy.mPermissionBasedAdmin = null;
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
policy.mUserControlDisabledPackages = null;
@@ -484,6 +508,10 @@
} catch (RuntimeException e) {
Slogf.w(TAG, e, "Failed loading admin %s", name);
}
+ } else if ("permission-based-admin".equals(tag)) {
+ ActiveAdmin ap = new ActiveAdmin(/* info= */ null, /* parent= */ false);
+ ap.readFromXml(parser, /* overwritePolicies= */ false);
+ policy.mPermissionBasedAdmin = ap;
} else if ("delegation".equals(tag)) {
// Parse delegation info.
final String delegatePackage = parser.getAttributeValue(null,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 71764dc..5013fb0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -33,6 +33,7 @@
import android.app.admin.PolicyUpdatesReceiver;
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
+import android.app.admin.UserRestrictionPolicyKey;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -555,8 +556,9 @@
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
return null;
}
- return getLocalPolicyStateLocked(policyDefinition, userId)
- .getPoliciesSetByAdmins().get(enforcingAdmin).getValue();
+ PolicyValue<V> value = getLocalPolicyStateLocked(policyDefinition, userId)
+ .getPoliciesSetByAdmins().get(enforcingAdmin);
+ return value == null ? null : value.getValue();
}
}
@@ -613,6 +615,47 @@
}
}
+ /**
+ * Returns all user restriction policies set by the given admin.
+ *
+ * <p>Pass in {@link UserHandle#USER_ALL} for {@code userId} to get global restrictions set by
+ * the admin
+ */
+ @NonNull
+ Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdmin(
+ @NonNull EnforcingAdmin admin,
+ int userId) {
+ Objects.requireNonNull(admin);
+ synchronized (mLock) {
+ if (userId == UserHandle.USER_ALL) {
+ return getUserRestrictionPolicyKeysForAdminLocked(mGlobalPolicies, admin);
+ }
+ if (!mLocalPolicies.contains(userId)) {
+ return Set.of();
+ }
+ return getUserRestrictionPolicyKeysForAdminLocked(mLocalPolicies.get(userId), admin);
+ }
+ }
+
+ private Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdminLocked(
+ Map<PolicyKey, PolicyState<?>> policies,
+ EnforcingAdmin admin) {
+ Set<UserRestrictionPolicyKey> keys = new HashSet<>();
+ for (PolicyKey key : policies.keySet()) {
+ if (!policies.get(key).getPolicyDefinition().isUserRestrictionPolicy()) {
+ continue;
+ }
+ // User restriction policies are always boolean
+ PolicyValue<Boolean> value = (PolicyValue<Boolean>) policies.get(key)
+ .getPoliciesSetByAdmins().get(admin);
+ if (value == null || !value.getValue()) {
+ continue;
+ }
+ keys.add((UserRestrictionPolicyKey) key);
+ }
+ return keys;
+ }
+
private <V> boolean hasLocalPolicyLocked(PolicyDefinition<V> policyDefinition, int userId) {
if (policyDefinition.isGlobalOnlyPolicy()) {
return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 22bd107..61ee0e7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
@@ -33,9 +34,12 @@
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
@@ -43,7 +47,6 @@
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
-import static android.app.admin.DevicePolicyManager.AUTO_TIMEZONE_POLICY;
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
@@ -61,6 +64,8 @@
import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_HIBERNATION;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -140,6 +145,9 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_TELEPHONY_PAUSED_BODY;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_TELEPHONY_PAUSED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_TELEPHONY_PAUSED_TURN_ON_BUTTON;
import static android.app.admin.ProvisioningException.ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED;
import static android.app.admin.ProvisioningException.ERROR_PRE_CONDITION_FAILED;
import static android.app.admin.ProvisioningException.ERROR_PROFILE_CREATION_FAILED;
@@ -147,6 +155,8 @@
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
+import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -235,6 +245,7 @@
import android.app.admin.IntegerPolicyValue;
import android.app.admin.IntentFilterPolicyKey;
import android.app.admin.LockTaskPolicy;
+import android.app.admin.LongPolicyValue;
import android.app.admin.ManagedProfileProvisioningParams;
import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.NetworkEvent;
@@ -253,6 +264,7 @@
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.UnsafeStateException;
+import android.app.admin.UserRestrictionPolicyKey;
import android.app.admin.WifiSsidPolicy;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
@@ -726,6 +738,11 @@
APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION);
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+ EXEMPT_FROM_HIBERNATION, OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION);
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+ EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION);
}
/**
@@ -1152,6 +1169,12 @@
} else if (ACTION_TURN_PROFILE_ON_NOTIFICATION.equals(action)) {
Slogf.i(LOG_TAG, "requesting to turn on the profile: " + userHandle);
mUserManager.requestQuietModeEnabled(false, UserHandle.of(userHandle));
+ } else if (ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
+ notifyIfManagedSubscriptionsAreUnavailable(
+ UserHandle.of(userHandle), /* managedProfileAvailable= */ false);
+ } else if (ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)) {
+ notifyIfManagedSubscriptionsAreUnavailable(
+ UserHandle.of(userHandle), /* managedProfileAvailable= */ true);
}
}
@@ -1329,7 +1352,7 @@
if (shouldMigrateToDevicePolicyEngine()) {
migratePoliciesToDevicePolicyEngine();
}
- if (isDevicePolicyEngineFlagEnabled()) {
+ if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handlePackageChanged(packageName, userHandle);
}
// Persist updates if the removed package was an admin or delegate.
@@ -1996,7 +2019,8 @@
filter.addAction(Intent.ACTION_USER_STOPPED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ filter.addAction(ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ filter.addAction(ACTION_MANAGED_PROFILE_AVAILABLE);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -2021,7 +2045,7 @@
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
mDeviceManagementResourcesProvider.load();
- if (isDevicePolicyEngineFlagEnabled()) {
+ if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.load();
}
@@ -2483,8 +2507,7 @@
if (profileOwner == null || !mUserManager.isManagedProfile(userId)) {
continue;
}
- maybeSetDefaultRestrictionsForAdminLocked(userId, profileOwner,
- UserRestrictionsUtils.getDefaultEnabledForManagedProfiles());
+ maybeSetDefaultRestrictionsForAdminLocked(userId, profileOwner);
ensureUnknownSourcesRestrictionForProfileOwnerLocked(
userId, profileOwner, false /* newOwner */);
}
@@ -2499,9 +2522,20 @@
ActiveAdmin profileOwner, boolean newOwner) {
if (newOwner || mInjector.settingsSecureGetIntForUser(
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId) != 0) {
- profileOwner.ensureUserRestrictions().putBoolean(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
- saveUserRestrictionsLocked(userId);
+ if (isDevicePolicyEngineEnabled()) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ profileOwner.info.getComponent(),
+ profileOwner.getUserHandle().getIdentifier()),
+ new BooleanPolicyValue(true),
+ userId);
+ } else {
+ profileOwner.ensureUserRestrictions().putBoolean(
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
+ saveUserRestrictionsLocked(userId);
+ }
mInjector.settingsSecurePutIntForUser(
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId);
}
@@ -2510,11 +2544,28 @@
/**
* Apply default restrictions that haven't been applied to a given admin yet.
*/
- private void maybeSetDefaultRestrictionsForAdminLocked(
- int userId, ActiveAdmin admin, Set<String> defaultRestrictions) {
+ private void maybeSetDefaultRestrictionsForAdminLocked(int userId, ActiveAdmin admin) {
+ Set<String> defaultRestrictions =
+ UserRestrictionsUtils.getDefaultEnabledForManagedProfiles();
if (defaultRestrictions.equals(admin.defaultEnabledRestrictionsAlreadySet)) {
return; // The same set of default restrictions has been already applied.
}
+ if (isDevicePolicyEngineEnabled()) {
+ for (String restriction : defaultRestrictions) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(),
+ admin.getUserHandle().getIdentifier()),
+ new BooleanPolicyValue(true),
+ userId);
+ }
+ admin.defaultEnabledRestrictionsAlreadySet.addAll(defaultRestrictions);
+ Slogf.i(LOG_TAG, "Enabled the following restrictions by default: " +
+ defaultRestrictions);
+ return;
+ }
+
Slogf.i(LOG_TAG, "New user restrictions need to be set by default for user " + userId);
if (VERBOSE_LOG) {
@@ -3365,7 +3416,7 @@
updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
startOwnerService(userId, "start-user");
- if (isDevicePolicyEngineFlagEnabled()) {
+ if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleStartUser(userId);
}
}
@@ -3390,7 +3441,7 @@
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
- if (isDevicePolicyEngineFlagEnabled()) {
+ if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleUnlockUser(userId);
}
}
@@ -3402,7 +3453,7 @@
void handleStopUser(int userId) {
updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT));
mDeviceAdminServiceController.stopServicesForUser(userId, /* actionForLog= */ "stop-user");
- if (isDevicePolicyEngineFlagEnabled()) {
+ if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleStopUser(userId);
}
}
@@ -4163,6 +4214,27 @@
}
/**
+ * Get the list of active admins for an affected user:
+ * <ul>
+ * <li>The active admins associated with the userHandle itself</li>
+ * <li>The parent active admins for each managed profile associated with the userHandle</li>
+ * <li>The permission based admin associated with the userHandle itself</li>
+ * </ul>
+ *
+ * @param userHandle the affected user for whom to get the active admins
+ * @return the list of active admins for the affected user
+ */
+ @GuardedBy("getLockObject()")
+ private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
+ int userHandle) {
+ List<ActiveAdmin> list = getActiveAdminsForAffectedUserLocked(userHandle);
+ if (getUserData(userHandle).mPermissionBasedAdmin != null) {
+ list.add(getUserData(userHandle).mPermissionBasedAdmin);
+ }
+ return list;
+ }
+
+ /**
* Returns the list of admins on the given user, as well as parent admins for each managed
* profile associated with the given user. Optionally also include the admin of each managed
* profile.
@@ -8467,24 +8539,39 @@
* Disables all device cameras according to the specified admin.
*/
@Override
- public void setCameraDisabled(ComponentName who, boolean disabled, boolean parent) {
+ public void setCameraDisabled(ComponentName who, String callerPackageName, boolean disabled,
+ boolean parent) {
if (!mHasFeature) {
return;
}
- Objects.requireNonNull(who, "ComponentName is null");
- final CallerIdentity caller = getCallerIdentity(who);
- if (parent) {
- Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ final CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+ final int userHandle = caller.getUserId();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED);
- final int userHandle = caller.getUserId();
+ ActiveAdmin admin = null;
+ if (isPermissionCheckFlagEnabled()) {
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ who,
+ MANAGE_DEVICE_POLICY_CAMERA,
+ callerPackageName,
+ getProfileParentUserIfRequested(userHandle, parent));
+ admin = enforcingAdmin.getActiveAdmin();
+ } else {
+ Objects.requireNonNull(who, "ComponentName is null");
+ if (parent) {
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller));
+ }
+ synchronized (getLockObject()) {
+ admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA, parent);
+ }
+ }
+
synchronized (getLockObject()) {
- ActiveAdmin ap = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA, parent);
- if (ap.disableCamera != disabled) {
- ap.disableCamera = disabled;
+ if (admin.disableCamera != disabled) {
+ admin.disableCamera = disabled;
saveSettingsLocked(userHandle);
}
}
@@ -8513,14 +8600,19 @@
if (!mHasFeature) {
return false;
}
-
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)
- || isCameraServerUid(caller));
-
- if (parent) {
+ if (isPermissionCheckFlagEnabled()) {
Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()));
+ hasFullCrossUsersPermission(caller, userHandle) || isCameraServerUid(caller)
+ || hasPermission(MANAGE_DEVICE_POLICY_CAMERA, userHandle)
+ || hasPermission(QUERY_ADMIN_POLICY));
+ } else {
+ Preconditions.checkCallAuthorization(
+ hasFullCrossUsersPermission(caller, userHandle) || isCameraServerUid(caller));
+ if (parent) {
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()));
+ }
}
synchronized (getLockObject()) {
@@ -8533,12 +8625,19 @@
if (deviceOwner != null && deviceOwner.disableCamera) {
return true;
}
- final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
+
// Return the strictest policy across all participating admins.
- List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
+ List<ActiveAdmin> admins;
+ final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
+ if (isPermissionCheckFlagEnabled()) {
+ admins = getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
+ affectedUserId);
+ } else {
+ admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
+ }
// Determine whether or not the device camera is disabled for any active admins.
- for (ActiveAdmin admin : admins) {
- if (admin.disableCamera) {
+ for (ActiveAdmin activeAdmin : admins) {
+ if (activeAdmin.disableCamera) {
return true;
}
}
@@ -9287,8 +9386,7 @@
mInjector.binderWithCleanCallingIdentity(() -> {
if (mUserManager.isManagedProfile(userHandle)) {
- maybeSetDefaultRestrictionsForAdminLocked(userHandle, admin,
- UserRestrictionsUtils.getDefaultEnabledForManagedProfiles());
+ maybeSetDefaultRestrictionsForAdminLocked(userHandle, admin);
ensureUnknownSourcesRestrictionForProfileOwnerLocked(userHandle, admin,
true /* newOwner */);
}
@@ -11432,7 +11530,7 @@
final int userId = user.id;
- if (isDevicePolicyEngineFlagEnabled()) {
+ if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleUserCreated(user);
}
@@ -12003,7 +12101,8 @@
}
@Override
- public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner,
+ public void setUserRestriction(
+ ComponentName who, String callerPackage, String key, boolean enabledFromThisOwner,
boolean parent) {
Objects.requireNonNull(who, "ComponentName is null");
@@ -12012,18 +12111,13 @@
if (!UserRestrictionsUtils.isValidRestriction(key)) {
return;
}
-
if (parent) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
}
-
int userId = caller.getUserId();
synchronized (getLockObject()) {
- final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(userId), parent);
-
if (isDefaultDeviceOwner(caller)) {
if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
throw new SecurityException("Device owner cannot set user restriction " + key);
@@ -12041,27 +12135,119 @@
boolean profileOwnerCanChangeOnItself = !parent
&& UserRestrictionsUtils.canProfileOwnerChange(
key, userId == getMainUserId());
- boolean orgOwnedProfileOwnerCanChangesGlobally = parent
+ boolean orgOwnedProfileOwnerCanChangeGlobally = parent
&& isProfileOwnerOfOrganizationOwnedDevice(caller)
&& UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
key);
- if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangesGlobally) {
+ if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
throw new SecurityException("Profile owner cannot set user restriction " + key);
}
}
- checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
-
- // Save the restriction to ActiveAdmin.
- final Bundle restrictions = activeAdmin.ensureUserRestrictions();
- if (enabledFromThisOwner) {
- restrictions.putBoolean(key, true);
- } else {
- restrictions.remove(key);
- }
- saveUserRestrictionsLocked(userId);
}
- final int eventId = enabledFromThisOwner
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
+
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ who, caller.getUserId());
+ PolicyDefinition<Boolean> policyDefinition =
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
+ if (enabledFromThisOwner) {
+ mDevicePolicyEngine.setLocalPolicy(
+ policyDefinition,
+ admin,
+ new BooleanPolicyValue(true),
+ parent ? getProfileParentId(userId) : userId);
+ } else {
+ // Remove any local and global policy that was set by the admin
+ if (!policyDefinition.isLocalOnlyPolicy()) {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ policyDefinition,
+ admin);
+ }
+ if (!policyDefinition.isGlobalOnlyPolicy()) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ policyDefinition,
+ admin,
+ userId);
+
+ int parentUserId = getProfileParentId(userId);
+ if (parentUserId != userId) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ policyDefinition,
+ admin,
+ parentUserId);
+ }
+ }
+ }
+ } else {
+ synchronized (getLockObject()) {
+ final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(userId), parent);
+ // Save the restriction to ActiveAdmin.
+ final Bundle restrictions = activeAdmin.ensureUserRestrictions();
+ if (enabledFromThisOwner) {
+ restrictions.putBoolean(key, true);
+ } else {
+ restrictions.remove(key);
+ }
+ saveUserRestrictionsLocked(userId);
+ }
+ }
+ logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
+ }
+
+ @Override
+ public void setUserRestrictionGlobally(String callerPackage, String key) {
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
+ // TODO: Replace with new permission checks, for now copying this over from
+ // setUserRestriction
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ return;
+ }
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
+ int userHandle = caller.getUserId();
+ if (isDefaultDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+ throw new SecurityException("Device owner cannot set user restriction " + key);
+ }
+ } else if (isFinancedDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+ throw new SecurityException("Cannot set user restriction " + key
+ + " when managing a financed device");
+ }
+ } else {
+ boolean profileOwnerCanChangeOnItself =
+ UserRestrictionsUtils.canProfileOwnerChange(key, userHandle == getMainUserId());
+ boolean orgOwnedProfileOwnerCanChangeGlobally =
+ isProfileOwnerOfOrganizationOwnedDevice(caller)
+ && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+ key);
+
+ if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
+ throw new SecurityException("Profile owner cannot set user restriction " + key);
+ }
+ }
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
+
+ if (!useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ throw new IllegalStateException("One or more admins are not targeting Android 14.");
+ }
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ caller.getPackageName(), caller.getUserId());
+
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(key),
+ admin,
+ new BooleanPolicyValue(true));
+
+ logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
+ }
+
+ private void logUserRestrictionCall(
+ String key, boolean enabled, boolean parent, CallerIdentity caller) {
+ final int eventId = enabled
? DevicePolicyEnums.ADD_USER_RESTRICTION
: DevicePolicyEnums.REMOVE_USER_RESTRICTION;
DevicePolicyEventLogger
@@ -12070,14 +12256,18 @@
.setStrings(key, parent ? CALLED_FROM_PARENT : NOT_CALLED_FROM_PARENT)
.write();
if (SecurityLog.isLoggingEnabled()) {
- final int eventTag = enabledFromThisOwner
+ final int eventTag = enabled
? SecurityLog.TAG_USER_RESTRICTION_ADDED
: SecurityLog.TAG_USER_RESTRICTION_REMOVED;
- SecurityLog.writeEvent(eventTag, who.getPackageName(), userId, key);
+ SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
}
}
private void saveUserRestrictionsLocked(int userId) {
+ if (isDevicePolicyEngineEnabled()) {
+ // User restrictions are handled in the policy engine
+ return;
+ }
saveSettingsLocked(userId);
pushUserRestrictions(userId);
sendChangedNotification(userId);
@@ -12139,7 +12329,7 @@
}
@Override
- public Bundle getUserRestrictions(ComponentName who, boolean parent) {
+ public Bundle getUserRestrictions(ComponentName who, String callerPackage, boolean parent) {
if (!mHasFeature) {
return null;
}
@@ -12151,14 +12341,59 @@
|| isProfileOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
- synchronized (getLockObject()) {
- final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
- return activeAdmin.userRestrictions;
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ return getUserRestrictionsFromPolicyEngine(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, caller.getUserId()),
+ parent ? getProfileParentId(caller.getUserId()) : caller.getUserId());
+ } else {
+ synchronized (getLockObject()) {
+ final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
+ return activeAdmin.userRestrictions;
+ }
}
}
@Override
+ public Bundle getUserRestrictionsGlobally(String callerPackage) {
+ if (!mHasFeature) {
+ return null;
+ }
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
+ if (!useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ throw new IllegalStateException("One or more admins are not targeting Android 14.");
+ }
+ // TODO: Replace with new permission checks, for now copying this over from
+ // setUserRestriction
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)
+ || isProfileOwner(caller));
+
+ return getUserRestrictionsFromPolicyEngine(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ caller.getPackageName(), caller.getUserId()),
+ UserHandle.USER_ALL);
+ }
+
+ /**
+ * Returns user restrictions set by the given admin for the provided {@code userId}.
+ *
+ * <p>Pass in {@link UserHandle#USER_ALL} for {@code userId} to get global restrictions set by
+ * the admin
+ */
+ private Bundle getUserRestrictionsFromPolicyEngine(EnforcingAdmin admin, int userId) {
+ Set<UserRestrictionPolicyKey> restrictionKeys =
+ mDevicePolicyEngine.getUserRestrictionPolicyKeysForAdmin(
+ admin,
+ userId);
+ Bundle restrictions = new Bundle();
+ for (UserRestrictionPolicyKey key : restrictionKeys) {
+ restrictions.putBoolean(key.getRestriction(), true);
+ }
+ return restrictions;
+ }
+
+ @Override
public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
boolean hidden, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
@@ -13568,7 +13803,8 @@
+ " should be used instead.");
} else {
try {
- setUserRestriction(who, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ setUserRestriction(who, who.getPackageName(),
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
(Integer.parseInt(value) == 0) ? true : false, /* parent */ false);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
@@ -13625,7 +13861,8 @@
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED);
synchronized (getLockObject()) {
- setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on, /* parent */ false);
+ setUserRestriction(who, who.getPackageName(), UserManager.DISALLOW_UNMUTE_DEVICE, on,
+ /* parent */ false);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED)
.setAdmin(who)
@@ -13771,6 +14008,26 @@
return false;
}
+ @Override
+ public boolean isStatusBarDisabled(String callerPackage) {
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+
+ int userId = caller.getUserId();
+ synchronized (getLockObject()) {
+ Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
+ "Admin " + callerPackage
+ + " is neither the device owner or affiliated user's profile owner.");
+ if (isManagedProfile(userId)) {
+ throw new SecurityException("Managed profile cannot disable status bar");
+ }
+ DevicePolicyData policy = getUserData(userId);
+ return policy.mStatusBarDisabled;
+ }
+ }
+
+
/**
* We need to update the internal state of whether a user has completed setup or a
* device has paired once. After that, we ignore any changes that reset the
@@ -15596,6 +15853,7 @@
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ caller.getPackageName(),
caller.getUserId());
admin = enforcingAdmin.getActiveAdmin();
} else {
@@ -15626,6 +15884,7 @@
EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ caller.getPackageName(),
caller.getUserId());
admin = enforcingAdmin.getActiveAdmin();
} else {
@@ -16997,23 +17256,52 @@
final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+ final int userId = caller.getUserId();
- synchronized (getLockObject()) {
- final int userHandle = caller.getUserId();
-
- DevicePolicyData policy = getUserData(userHandle);
- return mInjector.binderWithCleanCallingIdentity(() -> {
- if (policy.mPasswordTokenHandle != 0) {
- mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, userHandle);
- }
- policy.mPasswordTokenHandle = mLockPatternUtils.addEscrowToken(token,
- userHandle, /*EscrowTokenStateChangeCallback*/ null);
- saveSettingsLocked(userHandle);
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin, userId);
+ Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
+ long tokenHandle = addEscrowToken(
+ token, currentTokenHandle == null ? 0 : currentTokenHandle, userId);
+ if (tokenHandle == 0) {
+ return false;
+ }
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ new LongPolicyValue(tokenHandle),
+ userId);
+ return true;
+ } else {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(userId);
+ policy.mPasswordTokenHandle = addEscrowToken(
+ token, policy.mPasswordTokenHandle, userId);
+ saveSettingsLocked(userId);
return policy.mPasswordTokenHandle != 0;
- });
+ }
}
}
+ private long addEscrowToken(byte[] token, long currentPasswordTokenHandle, int userId) {
+ resetEscrowToken(currentPasswordTokenHandle, userId);
+ return mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.addEscrowToken(
+ token, userId, /* EscrowTokenStateChangeCallback= */ null));
+ }
+
+ private boolean resetEscrowToken(long tokenHandle, int userId) {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ if (tokenHandle != 0) {
+ return mLockPatternUtils.removeEscrowToken(tokenHandle, userId);
+ }
+ return false;
+ });
+ }
+
@Override
public boolean clearResetPasswordToken(ComponentName admin) {
if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
@@ -17022,22 +17310,34 @@
final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+ final int userId = caller.getUserId();
+ boolean result = false;
- synchronized (getLockObject()) {
- final int userHandle = caller.getUserId();
-
- DevicePolicyData policy = getUserData(userHandle);
- if (policy.mPasswordTokenHandle != 0) {
- return mInjector.binderWithCleanCallingIdentity(() -> {
- boolean result = mLockPatternUtils.removeEscrowToken(
- policy.mPasswordTokenHandle, userHandle);
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin, userId);
+ Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
+ if (currentTokenHandle != null) {
+ result = resetEscrowToken(currentTokenHandle, userId);
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
+ }
+ } else {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(userId);
+ if (policy.mPasswordTokenHandle != 0) {
+ result = resetEscrowToken(policy.mPasswordTokenHandle, userId);
policy.mPasswordTokenHandle = 0;
- saveSettingsLocked(userHandle);
- return result;
- });
+ saveSettingsLocked(userId);
+ }
}
}
- return false;
+ return result;
}
@Override
@@ -17049,16 +17349,30 @@
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- synchronized (getLockObject()) {
- return isResetPasswordTokenActiveForUserLocked(caller.getUserId());
+ int userId = caller.getUserId();
+
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin, userId);
+ Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
+ return isResetPasswordTokenActiveForUserLocked(
+ currentTokenHandle == null ? 0 : currentTokenHandle, userId);
+ } else {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(userId);
+ return isResetPasswordTokenActiveForUserLocked(policy.mPasswordTokenHandle, userId);
+ }
}
}
- private boolean isResetPasswordTokenActiveForUserLocked(int userHandle) {
- DevicePolicyData policy = getUserData(userHandle);
- if (policy.mPasswordTokenHandle != 0) {
+ private boolean isResetPasswordTokenActiveForUserLocked(
+ long passwordTokenHandle, int userHandle) {
+ if (passwordTokenHandle != 0) {
return mInjector.binderWithCleanCallingIdentity(() ->
- mLockPatternUtils.isEscrowTokenActive(policy.mPasswordTokenHandle, userHandle));
+ mLockPatternUtils.isEscrowTokenActive(passwordTokenHandle, userHandle));
}
return false;
}
@@ -17075,24 +17389,41 @@
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- synchronized (getLockObject()) {
- DevicePolicyData policy = getUserData(caller.getUserId());
- if (policy.mPasswordTokenHandle != 0) {
- final String password = passwordOrNull != null ? passwordOrNull : "";
- final boolean result = resetPasswordInternal(password, policy.mPasswordTokenHandle,
- token, flags, caller);
- if (result) {
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
- .setAdmin(caller.getComponentName())
- .write();
- }
- return result;
+ int userId = caller.getUserId();
+ boolean result = false;
+ final String password = passwordOrNull != null ? passwordOrNull : "";
+
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin, userId);
+ Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
+ if (currentTokenHandle != null && currentTokenHandle != 0) {
+ result = resetPasswordInternal(password, currentTokenHandle, token, flags, caller);
} else {
Slogf.w(LOG_TAG, "No saved token handle");
}
+ } else {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(userId);
+ if (policy.mPasswordTokenHandle != 0) {
+ result = resetPasswordInternal(
+ password, policy.mPasswordTokenHandle, token, flags, caller);
+ } else {
+ Slogf.w(LOG_TAG, "No saved token handle");
+ }
+ }
}
- return false;
+
+ if (result) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
+ .setAdmin(caller.getComponentName())
+ .write();
+ }
+ return result;
}
@Override
@@ -18143,6 +18474,15 @@
}
});
}
+ String[] appOpExemptions = new String[exemptions.length];
+ for (int i = 0; i < exemptions.length; i++) {
+ appOpExemptions[i] = APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.get(exemptions[i]);
+ }
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
+ .setAdmin(caller.getPackageName())
+ .setStrings(packageName, appOpExemptions)
+ .write();
}
@Override
@@ -18602,6 +18942,83 @@
});
}
+ private void notifyIfManagedSubscriptionsAreUnavailable(
+ UserHandle managedProfile, boolean managedProfileAvailable) {
+ if (!isManagedProfile(managedProfile.getIdentifier())) {
+ Slog.wtf(
+ LOG_TAG,
+ "Expected managed profile when notified of profile availability change.");
+ }
+ if (getManagedSubscriptionsPolicy().getPolicyType()
+ != ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ // There may be a subscription in the personal profile, in which case calls and
+ // texts may still be available. No need to notify the user.
+ return;
+ }
+ if (managedProfileAvailable) {
+ // When quiet mode is switched off calls and texts then become available to the user,
+ // so no need to keep showing the notification.
+ mInjector
+ .getNotificationManager()
+ .cancel(SystemMessage.NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF);
+ return;
+ }
+ final Intent intent = new Intent(ACTION_TURN_PROFILE_ON_NOTIFICATION);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, managedProfile.getIdentifier());
+ final PendingIntent pendingIntent =
+ mInjector.pendingIntentGetBroadcast(
+ mContext,
+ /* requestCode= */ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ final Notification.Action turnProfileOnButton =
+ new Notification.Action.Builder(
+ /* icon= */ null, getUnpauseWorkAppsButtonText(), pendingIntent)
+ .build();
+
+ final Bundle extras = new Bundle();
+ extras.putString(
+ Notification.EXTRA_SUBSTITUTE_APP_NAME, getWorkProfileContentDescription());
+ final Notification notification =
+ new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
+ .setSmallIcon(R.drawable.ic_phone_disabled)
+ .setContentTitle(getUnpauseWorkAppsForTelephonyTitle())
+ .setContentText(getUnpauseWorkAppsForTelephonyText())
+ .setStyle(new Notification.BigTextStyle().bigText(
+ getUnpauseWorkAppsForTelephonyText()))
+ .addAction(turnProfileOnButton)
+ .addExtras(extras)
+ .setOngoing(false)
+ .setShowWhen(true)
+ .setAutoCancel(true)
+ .build();
+
+ mInjector
+ .getNotificationManager()
+ .notifyAsUser(
+ /* tag= */ null,
+ SystemMessage.NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF,
+ notification,
+ UserHandle.of(getProfileParentId(managedProfile.getIdentifier())));
+ }
+
+ private String getUnpauseWorkAppsButtonText() {
+ return getUpdatableString(
+ WORK_PROFILE_TELEPHONY_PAUSED_TURN_ON_BUTTON,
+ R.string.work_profile_telephony_paused_turn_on_button);
+ }
+
+ private String getUnpauseWorkAppsForTelephonyTitle() {
+ return getUpdatableString(
+ WORK_PROFILE_TELEPHONY_PAUSED_TITLE, R.string.work_profile_telephony_paused_title);
+ }
+
+ private String getUnpauseWorkAppsForTelephonyText() {
+ return getUpdatableString(
+ WORK_PROFILE_TELEPHONY_PAUSED_BODY,
+ R.string.work_profile_telephony_paused_text);
+ }
+
@GuardedBy("getLockObject()")
private void updateProfileOffDeadlineNotificationLocked(
int profileUserId, ActiveAdmin profileOwner, int notificationState) {
@@ -18785,9 +19202,11 @@
"call canProfileOwnerResetPasswordWhenLocked"));
synchronized (getLockObject()) {
final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
+ DevicePolicyData policy = getUserData(userId);
if (poAdmin == null
|| getEncryptionStatus() != ENCRYPTION_STATUS_ACTIVE_PER_USER
- || !isResetPasswordTokenActiveForUserLocked(userId)) {
+ || !isResetPasswordTokenActiveForUserLocked(
+ policy.mPasswordTokenHandle, userId)) {
return false;
}
final ApplicationInfo poAppInfo;
@@ -18970,7 +19389,7 @@
}
setUserSetupComplete(userInfo.id);
- startUser(userInfo.id, callerPackage);
+ startProfileForSetup(userInfo.id, callerPackage);
maybeMigrateAccount(
userInfo.id, caller.getUserId(), provisioningParams.getAccountToMigrate(),
provisioningParams.isKeepingAccountOnMigration(), callerPackage);
@@ -19201,8 +19620,9 @@
mContext.getContentResolver(), USER_SETUP_COMPLETE, 1, userId);
}
- private void startUser(@UserIdInt int userId, String callerPackage)
+ private void startProfileForSetup(@UserIdInt int userId, String callerPackage)
throws IllegalStateException {
+ Slogf.i(LOG_TAG, "Starting profile %d as requested by package %s", userId, callerPackage);
final long startTime = SystemClock.elapsedRealtime();
final UserUnlockedBlockingReceiver unlockedReceiver = new UserUnlockedBlockingReceiver(
userId);
@@ -19213,7 +19633,8 @@
/* broadcastPermission = */ null,
/* scheduler= */ null);
try {
- if (!mInjector.getIActivityManager().startUserInBackground(userId)) {
+ // Must call startProfileEvenWhenDisabled(), as profile is not enabled yet
+ if (!mInjector.getActivityManagerInternal().startProfileEvenWhenDisabled(userId)) {
throw new ServiceSpecificException(ERROR_STARTING_PROFILE_FAILED,
String.format("Unable to start user %d in background", userId));
}
@@ -19226,9 +19647,6 @@
DevicePolicyEnums.PLATFORM_PROVISIONING_START_PROFILE_MS,
startTime,
callerPackage);
- } catch (RemoteException e) {
- // Shouldn't happen.
- Slogf.wtf(LOG_TAG, "Error starting user", e);
} finally {
mContext.unregisterReceiver(unlockedReceiver);
}
@@ -20325,9 +20743,9 @@
* the associated cross-user permission if the caller's user is different to the target user.
*/
private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
- String permission, int targetUserId) {
+ String permission, String callerPackageName, int targetUserId) {
enforcePermission(permission, targetUserId);
- return getEnforcingAdminForCaller(admin, getCallerIdentity());
+ return getEnforcingAdminForCaller(admin, callerPackageName);
}
/**
@@ -20342,9 +20760,9 @@
* the associated cross-user permission if the caller's user is different to the target user.
*/
private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin,
- String permission, int targetUserId) {
+ String permission, String callerPackageName, int targetUserId) {
enforceCanQuery(permission, targetUserId);
- return getEnforcingAdminForCaller(admin, getCallerIdentity());
+ return getEnforcingAdminForCaller(admin, callerPackageName);
}
private static final HashMap<String, String> POLICY_IDENTIFIER_TO_PERMISSION = new HashMap<>();
@@ -20488,7 +20906,8 @@
}
private EnforcingAdmin getEnforcingAdminForCaller(@Nullable ComponentName who,
- CallerIdentity caller) {
+ String callerPackageName) {
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
int userId = caller.getUserId();
ActiveAdmin admin = null;
synchronized (getLockObject()) {
@@ -20500,7 +20919,10 @@
if (getActiveAdminUncheckedLocked(who, userId) != null) {
return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
}
- return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId);
+ if (admin == null) {
+ admin = getUserData(userId).createOrGetPermissionBasedAdmin();
+ }
+ return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
}
private boolean isPermissionCheckFlagEnabled() {
@@ -21047,10 +21469,14 @@
}
return true;
} else {
- return isDevicePolicyEngineFlagEnabled() && !hasDPCsNotSupportingCoexistence();
+ return isDevicePolicyEngineEnabled();
}
}
+ private boolean isDevicePolicyEngineEnabled() {
+ return isDevicePolicyEngineFlagEnabled() && !hasDPCsNotSupportingCoexistence();
+ }
+
private boolean isDevicePolicyEngineFlagEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_DEVICE_POLICY_MANAGER,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 10e972d..7ed148b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -19,10 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.Authority;
-import android.app.admin.UnknownAuthority;
import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
import android.app.admin.RoleAuthority;
+import android.app.admin.UnknownAuthority;
import android.content.ComponentName;
import android.os.UserHandle;
@@ -71,9 +71,10 @@
private final boolean mIsRoleAuthority;
private final ActiveAdmin mActiveAdmin;
- static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
+ static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
+ ActiveAdmin admin) {
Objects.requireNonNull(packageName);
- return new EnforcingAdmin(packageName, userId);
+ return new EnforcingAdmin(packageName, userId, admin);
}
static EnforcingAdmin createEnterpriseEnforcingAdmin(
@@ -92,6 +93,15 @@
activeAdmin);
}
+
+ static EnforcingAdmin createEnterpriseEnforcingAdmin(
+ @NonNull String packageName, int userId) {
+ Objects.requireNonNull(packageName);
+ return new EnforcingAdmin(
+ packageName, /* componentName= */ null, Set.of(DPC_AUTHORITY), userId,
+ /* activeAdmin= */ null);
+ }
+
static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
@@ -111,11 +121,27 @@
return ROLE_AUTHORITY_PREFIX + roleName;
}
+ static Authority getParcelableAuthority(String authority) {
+ if (authority == null || authority.isEmpty()) {
+ return UnknownAuthority.UNKNOWN_AUTHORITY;
+ }
+ if (DPC_AUTHORITY.equals(authority)) {
+ return DpcAuthority.DPC_AUTHORITY;
+ }
+ if (DEVICE_ADMIN_AUTHORITY.equals(authority)) {
+ return DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
+ }
+ if (authority.startsWith(ROLE_AUTHORITY_PREFIX)) {
+ String role = authority.substring(ROLE_AUTHORITY_PREFIX.length());
+ return new RoleAuthority(Set.of(role));
+ }
+ return UnknownAuthority.UNKNOWN_AUTHORITY;
+ }
+
private EnforcingAdmin(
- String packageName, ComponentName componentName, Set<String> authorities, int userId,
- ActiveAdmin activeAdmin) {
+ String packageName, @Nullable ComponentName componentName, Set<String> authorities,
+ int userId, @Nullable ActiveAdmin activeAdmin) {
Objects.requireNonNull(packageName);
- Objects.requireNonNull(componentName);
Objects.requireNonNull(authorities);
// Role authorities should not be using this constructor
@@ -127,7 +153,7 @@
mActiveAdmin = activeAdmin;
}
- private EnforcingAdmin(String packageName, int userId) {
+ private EnforcingAdmin(String packageName, int userId, ActiveAdmin activeAdmin) {
Objects.requireNonNull(packageName);
// Only role authorities use this constructor.
@@ -137,7 +163,7 @@
mComponentName = null;
// authorities will be loaded when needed
mAuthorities = null;
- mActiveAdmin = null;
+ mActiveAdmin = activeAdmin;
}
private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -244,10 +270,12 @@
@Override
public int hashCode() {
if (mIsRoleAuthority) {
- // TODO(b/256854977): should we add UserId?
- return Objects.hash(mPackageName);
+ return Objects.hash(mPackageName, mUserId);
} else {
- return Objects.hash(mComponentName, getAuthorities());
+ return Objects.hash(
+ mComponentName == null ? mPackageName : mComponentName,
+ mUserId,
+ getAuthorities());
}
}
@@ -256,8 +284,10 @@
serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
if (!mIsRoleAuthority) {
- serializer.attribute(
- /* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
+ if (mComponentName != null) {
+ serializer.attribute(
+ /* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
+ }
// Role authorities get recomputed on load so no need to save them.
serializer.attribute(
/* namespace= */ null,
@@ -274,10 +304,11 @@
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
if (isRoleAuthority) {
- return new EnforcingAdmin(packageName, userId);
+ return new EnforcingAdmin(packageName, userId, null);
} else {
String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
- ComponentName componentName = new ComponentName(packageName, className);
+ ComponentName componentName = className == null
+ ? null : new ComponentName(packageName, className);
Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
new file mode 100644
index 0000000..f77d051
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.LongPolicyValue;
+import android.app.admin.PolicyKey;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+final class LongPolicySerializer extends PolicySerializer<Long> {
+
+ @Override
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+ @NonNull Long value) throws IOException {
+ Objects.requireNonNull(value);
+ serializer.attributeLong(/* namespace= */ null, attributeName, value);
+ }
+
+ @Nullable
+ @Override
+ LongPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ try {
+ return new LongPolicyValue(
+ parser.getAttributeLong(/* namespace= */ null, attributeName));
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing Long policy value", e);
+ return null;
+ }
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index ab6f732..a08c2054 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.BooleanPolicyValue;
+import android.app.admin.DevicePolicyIdentifiers;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IntegerPolicyValue;
import android.app.admin.IntentFilterPolicyKey;
@@ -28,10 +29,12 @@
import android.app.admin.PackagePolicyKey;
import android.app.admin.PolicyKey;
import android.app.admin.PolicyValue;
+import android.app.admin.UserRestrictionPolicyKey;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
import android.os.Bundle;
+import android.os.UserManager;
import com.android.internal.util.function.QuadFunction;
import com.android.modules.utils.TypedXmlPullParser;
@@ -40,6 +43,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -66,6 +70,11 @@
// global policy please add support.
private static final int POLICY_FLAG_NON_COEXISTABLE_POLICY = 1 << 3;
+ // Add this flag to any policy that is a user restriction, the reason for this is that there
+ // are some special APIs to handle user restriction policies and this is the way we can identify
+ // them.
+ private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4;
+
private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
@@ -73,7 +82,7 @@
List.of(new BooleanPolicyValue(true), new BooleanPolicyValue(false)));
static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
- new NoArgsPolicyKey(DevicePolicyManager.AUTO_TIMEZONE_POLICY),
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY),
// auto timezone is disabled by default, hence enabling it is more restrictive.
TRUE_MORE_RESTRICTIVE,
POLICY_FLAG_GLOBAL_ONLY_POLICY,
@@ -86,7 +95,7 @@
// when reading the policies from xml.
static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
new PolicyDefinition<>(
- new PackagePermissionPolicyKey(DevicePolicyManager.PERMISSION_GRANT_POLICY),
+ new PackagePermissionPolicyKey(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY),
// TODO: is this really the best mechanism, what makes denied more
// restrictive than
// granted?
@@ -113,16 +122,17 @@
}
return GENERIC_PERMISSION_GRANT.createPolicyDefinition(
new PackagePermissionPolicyKey(
- DevicePolicyManager.PERMISSION_GRANT_POLICY,
+ DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
packageName,
permissionName));
}
static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
- new NoArgsPolicyKey(DevicePolicyManager.LOCK_TASK_POLICY),
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.LOCK_TASK_POLICY),
new TopPriority<>(List.of(
// TODO(b/258166155): add correct device lock role name
- EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
+ EnforcingAdmin.getRoleAuthorityOf(
+ "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER"),
EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_LOCAL_ONLY_POLICY,
(LockTaskPolicy value, Context context, Integer userId, PolicyKey policyKey) ->
@@ -131,7 +141,8 @@
static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES =
new PolicyDefinition<>(
- new NoArgsPolicyKey(DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY),
+ new NoArgsPolicyKey(
+ DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
new StringSetUnion(),
(Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
@@ -143,10 +154,11 @@
static PolicyDefinition<ComponentName> GENERIC_PERSISTENT_PREFERRED_ACTIVITY =
new PolicyDefinition<>(
new IntentFilterPolicyKey(
- DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY),
+ DevicePolicyIdentifiers.PERSISTENT_PREFERRED_ACTIVITY_POLICY),
new TopPriority<>(List.of(
// TODO(b/258166155): add correct device lock role name
- EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
+ EnforcingAdmin.getRoleAuthorityOf(
+ "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER"),
EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_LOCAL_ONLY_POLICY,
PolicyEnforcerCallbacks::addPersistentPreferredActivity,
@@ -163,7 +175,8 @@
}
return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition(
new IntentFilterPolicyKey(
- DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY, intentFilter));
+ DevicePolicyIdentifiers.PERSISTENT_PREFERRED_ACTIVITY_POLICY,
+ intentFilter));
}
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
@@ -172,7 +185,7 @@
static PolicyDefinition<Boolean> GENERIC_PACKAGE_UNINSTALL_BLOCKED =
new PolicyDefinition<>(
new PackagePolicyKey(
- DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY),
+ DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY),
TRUE_MORE_RESTRICTIVE,
POLICY_FLAG_LOCAL_ONLY_POLICY,
PolicyEnforcerCallbacks::setUninstallBlocked,
@@ -189,7 +202,7 @@
}
return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition(
new PackagePolicyKey(
- DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName));
+ DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName));
}
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
@@ -198,7 +211,9 @@
static PolicyDefinition<Bundle> GENERIC_APPLICATION_RESTRICTIONS =
new PolicyDefinition<>(
new PackagePolicyKey(
- DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY),
+ DevicePolicyIdentifiers.APPLICATION_RESTRICTIONS_POLICY),
+ // Don't need to take in a resolution mechanism since its never used, but might
+ // need some refactoring to not always assume a non-null mechanism.
new MostRecent<>(),
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
// Application restrictions are now stored and retrieved from DPMS, so no
@@ -218,21 +233,137 @@
}
return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition(
new PackagePolicyKey(
- DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY, packageName));
+ DevicePolicyIdentifiers.APPLICATION_RESTRICTIONS_POLICY, packageName));
}
- private static final Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
- DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
- DevicePolicyManager.PERMISSION_GRANT_POLICY, GENERIC_PERMISSION_GRANT,
- DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK,
- DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY,
- USER_CONTROLLED_DISABLED_PACKAGES,
- DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY,
- GENERIC_PERSISTENT_PREFERRED_ACTIVITY,
- DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, GENERIC_PACKAGE_UNINSTALL_BLOCKED,
- DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY, GENERIC_APPLICATION_RESTRICTIONS
- );
+ static PolicyDefinition<Long> RESET_PASSWORD_TOKEN = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.RESET_PASSWORD_TOKEN_POLICY),
+ // Don't need to take in a resolution mechanism since its never used, but might
+ // need some refactoring to not always assume a non-null mechanism.
+ new MostRecent<>(),
+ POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
+ // DevicePolicyManagerService handles the enforcement, this just takes care of storage
+ (Long value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ new LongPolicySerializer());
+ private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
+ private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
+
+ static {
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
+ GENERIC_PERMISSION_GRANT);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.LOCK_TASK_POLICY, LOCK_TASK);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY,
+ USER_CONTROLLED_DISABLED_PACKAGES);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERSISTENT_PREFERRED_ACTIVITY_POLICY,
+ GENERIC_PERSISTENT_PREFERRED_ACTIVITY);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY,
+ GENERIC_PACKAGE_UNINSTALL_BLOCKED);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.APPLICATION_RESTRICTIONS_POLICY,
+ GENERIC_APPLICATION_RESTRICTIONS);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.RESET_PASSWORD_TOKEN_POLICY,
+ RESET_PASSWORD_TOKEN);
+
+ // User Restriction Policies
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_WIFI, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_CHANGE_WIFI_STATE, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_ADD_WIFI_CONFIG, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_LOCALE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_INSTALL_APPS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNINSTALL_APPS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SHARE_LOCATION, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_AIRPLANE_MODE, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BRIGHTNESS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AMBIENT_DISPLAY, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+ POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DEBUGGING_FEATURES, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_VPN, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_LOCATION, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_CLONE_PROFILE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.ENSURE_VERIFY_APPS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_APPS_CONTROL, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_MICROPHONE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADJUST_VOLUME, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OUTGOING_CALLS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SMS, /* flags= */ 0);
+ // TODO: check if its global only
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FUN, /* flags= */ 0);
+ // TODO: check if its global only
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CREATE_WINDOWS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, /* flags= */ 0);
+ // TODO: check if its global only
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OUTGOING_BEAM, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_WALLPAPER, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_WALLPAPER, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SAFE_BOOT, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_RECORD_AUDIO, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_RUN_IN_BACKGROUND, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0);
+ // TODO: double check flags
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONTENT_CAPTURE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONTENT_SUGGESTIONS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_USER_SWITCH, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_PRINTING, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0);
+ // TODO: check if its global only
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DEFAULT_APPS, /* flags= */ 0);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_CELLULAR_2G, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+
+ for (String key : USER_RESTRICTION_FLAGS.keySet()) {
+ createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
+ }
+ }
private final PolicyKey mPolicyKey;
private final ResolutionMechanism<V> mResolutionMechanism;
@@ -247,6 +378,17 @@
mPolicyEnforcerCallback, mPolicySerializer);
}
+ static PolicyDefinition<Boolean> getPolicyDefinitionForUserRestriction(
+ @UserManager.UserRestrictionKey String restriction) {
+ String key = DevicePolicyIdentifiers.getIdentifierForUserRestriction(restriction);
+
+ if (!POLICY_DEFINITIONS.containsKey(key)) {
+ throw new IllegalArgumentException("Unsupported user restriction " + restriction);
+ }
+ // All user restrictions are of type boolean
+ return (PolicyDefinition<Boolean>) POLICY_DEFINITIONS.get(key);
+ }
+
@NonNull
PolicyKey getPolicyKey() {
return mPolicyKey;
@@ -285,6 +427,10 @@
return (mPolicyFlags & POLICY_FLAG_NON_COEXISTABLE_POLICY) != 0;
}
+ boolean isUserRestrictionPolicy() {
+ return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0;
+ }
+
@Nullable
PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
return mResolutionMechanism.resolve(adminsPolicy);
@@ -294,6 +440,21 @@
return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
}
+ private static void createAndAddUserRestrictionPolicyDefinition(
+ String restriction, int flags) {
+ String identifier = DevicePolicyIdentifiers.getIdentifierForUserRestriction(restriction);
+ UserRestrictionPolicyKey key = new UserRestrictionPolicyKey(identifier, restriction);
+ flags |= POLICY_FLAG_USER_RESTRICTION_POLICY;
+ PolicyDefinition<Boolean> definition = new PolicyDefinition<>(
+ key,
+ TRUE_MORE_RESTRICTIVE,
+ flags,
+ PolicyEnforcerCallbacks::setUserRestriction,
+ new BooleanPolicySerializer());
+ POLICY_DEFINITIONS.put(key.getIdentifier(), definition);
+ }
+
+
/**
* Callers must ensure that {@code policyType} have implemented an appropriate
* {@link Object#equals} implementation.
@@ -340,7 +501,7 @@
// TODO: can we avoid casting?
PolicyKey policyKey = readPolicyKeyFromXml(parser);
PolicyDefinition<V> genericPolicyDefinition =
- (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.getIdentifier());
+ (PolicyDefinition<V>) POLICY_DEFINITIONS.get(policyKey.getIdentifier());
return genericPolicyDefinition.createPolicyDefinition(policyKey);
}
@@ -349,7 +510,7 @@
// TODO: can we avoid casting?
PolicyKey policyKey = PolicyKey.readGenericPolicyKeyFromXml(parser);
PolicyDefinition<PolicyValue<V>> genericPolicyDefinition =
- (PolicyDefinition<PolicyValue<V>>) sPolicyDefinitions.get(
+ (PolicyDefinition<PolicyValue<V>>) POLICY_DEFINITIONS.get(
policyKey.getIdentifier());
return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4ae7ca6..daa8a26 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -25,6 +25,7 @@
import android.app.admin.PackagePermissionPolicyKey;
import android.app.admin.PackagePolicyKey;
import android.app.admin.PolicyKey;
+import android.app.admin.UserRestrictionPolicyKey;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
@@ -40,6 +41,7 @@
import android.util.Slog;
import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
import java.util.Collections;
@@ -202,4 +204,22 @@
return true;
}));
}
+
+ static boolean setUserRestriction(
+ @Nullable Boolean enabled, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ if (!(policyKey instanceof UserRestrictionPolicyKey)) {
+ throw new IllegalArgumentException("policyKey is not of type "
+ + "UserRestrictionPolicyKey");
+ }
+ UserRestrictionPolicyKey parsedKey =
+ (UserRestrictionPolicyKey) policyKey;
+ // TODO: call into new UserManager API when merged
+ UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
+// userManager.setUserRestriction(
+// userId, parsedKey.getRestriction(), enabled != null && enabled);
+ return true;
+ }));
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
index 839840b..825157f0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
@@ -17,8 +17,10 @@
package com.android.server.devicepolicy;
import android.annotation.NonNull;
+import android.app.admin.Authority;
import android.app.admin.PolicyValue;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -54,7 +56,15 @@
@Override
android.app.admin.TopPriority<V> getParcelableResolutionMechanism() {
- return new android.app.admin.TopPriority<>(mHighestToLowestPriorityAuthorities);
+ return new android.app.admin.TopPriority<>(getParcelableAuthorities());
+ }
+
+ private List<Authority> getParcelableAuthorities() {
+ List<Authority> authorities = new ArrayList<>();
+ for (String authority : mHighestToLowestPriorityAuthorities) {
+ authorities.add(EnforcingAdmin.getParcelableAuthority(authority));
+ }
+ return authorities;
}
@Override
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
index a1853b4..50113fe 100644
--- a/services/java/com/android/server/HsumBootUserInitializer.java
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -113,11 +113,7 @@
UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN,
/* disallowedPackages= */ null,
/* token= */ null);
- if (newInitialUser == null) {
- Slogf.wtf(TAG, "Initial bootable MainUser creation failed: returned null");
- } else {
- Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id);
- }
+ Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id);
} catch (UserManager.CheckedUserOperationException e) {
Slogf.wtf(TAG, "Initial bootable MainUser creation failed", e);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b117cae..850b5b6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2095,6 +2095,14 @@
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
t.traceEnd();
+ t.traceBegin("StartTimeDetectorService");
+ try {
+ mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+ } catch (Throwable e) {
+ reportWtf("starting TimeDetectorService service", e);
+ }
+ t.traceEnd();
+
t.traceBegin("StartLocationManagerService");
mSystemServiceManager.startService(LocationManagerService.Lifecycle.class);
t.traceEnd();
@@ -2108,14 +2116,6 @@
}
t.traceEnd();
- t.traceBegin("StartTimeDetectorService");
- try {
- mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
- } catch (Throwable e) {
- reportWtf("starting TimeDetectorService service", e);
- }
- t.traceEnd();
-
t.traceBegin("StartTimeZoneDetectorService");
try {
mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 61f8681..ef35010 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -18,6 +18,7 @@
import android.Manifest
import android.app.ActivityManager
+import android.app.AppOpsManager
import android.compat.annotation.ChangeId
import android.compat.annotation.EnabledAfter
import android.content.Context
@@ -59,10 +60,12 @@
import com.android.server.ServiceThread
import com.android.server.SystemConfig
import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.UidAppOpPolicy
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.util.andInv
import com.android.server.permission.access.util.hasAnyBit
@@ -733,18 +736,46 @@
}
}
- private fun grantRequestedRuntimePermissions(
+ private fun setRequestedPermissionStates(
packageState: PackageState,
userId: Int,
- permissionNames: IndexedList<String>
+ permissionStates: IndexedMap<String, Int>
) {
service.mutateState {
- permissionNames.forEachIndexed { _, permissionName ->
- setRuntimePermissionGranted(
- packageState, userId, permissionName, isGranted = true,
- canManageRolePermission = false, overridePolicyFixed = false,
- reportError = false, "grantRequestedRuntimePermissions"
- )
+ permissionStates.forEachIndexed { _, permissionName, permissionState ->
+ when (permissionState) {
+ PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED,
+ PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {}
+ else -> {
+ Log.w(
+ LOG_TAG, "setRequestedPermissionStates: Unknown permission state" +
+ " $permissionState for permission $permissionName"
+ )
+ return@forEachIndexed
+ }
+ }
+ if (permissionName !in packageState.androidPackage!!.requestedPermissions) {
+ return@forEachIndexed
+ }
+ val permission = with(policy) { getPermissions()[permissionName] }
+ ?: return@forEachIndexed
+ when {
+ permission.isDevelopment || permission.isRuntime -> {
+ if (permissionState ==
+ PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) {
+ setRuntimePermissionGranted(
+ packageState, userId, permissionName, isGranted = true,
+ canManageRolePermission = false, overridePolicyFixed = false,
+ reportError = false, "setRequestedPermissionStates"
+ )
+ }
+ }
+ permission.isAppOp -> setAppOpPermissionGranted(
+ packageState, userId, permissionName,
+ permissionState == PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
+ )
+ else -> {}
+ }
}
}
}
@@ -890,6 +921,18 @@
}
}
+ private fun MutateStateScope.setAppOpPermissionGranted(
+ packageState: PackageState,
+ userId: Int,
+ permissionName: String,
+ isGranted: Boolean
+ ) {
+ val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as UidAppOpPolicy
+ val appOpName = AppOpsManager.permissionToOp(permissionName)
+ val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
+ with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
+ }
+
override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
if (!userManagerInternal.exists(userId)) {
Log.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
@@ -1814,15 +1857,7 @@
val packageState =
packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!!
// TODO: Add allowlisting
- grantRequestedRuntimePermissions(
- packageState,
- userId,
- params.permissionStates.mapNotNullIndexed { _, permissionName, permissionState ->
- permissionName.takeIf {
- permissionState == PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
- }
- }
- )
+ setRequestedPermissionStates(packageState, userId, params.permissionStates)
}
}
diff --git a/services/proguard.flags b/services/proguard.flags
index c133044..c31abbb 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -95,7 +95,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
--keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorCallback { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java b/services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java
new file mode 100644
index 0000000..3e2e46c
--- /dev/null
+++ b/services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+import static com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.MAX_NETWORK_TIME_AGE_MILLIS;
+import static com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.NTP_REFRESH_INTERVAL_MILLIS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.app.time.UnixEpochTime;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.location.gnss.NetworkTimeHelper.InjectTimeCallback;
+import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.Environment;
+import com.android.server.timedetector.NetworkTimeSuggestion;
+import com.android.server.timezonedetector.StateChangeListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Unit tests for {@link TimeDetectorNetworkTimeHelper}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class TimeDetectorNetworkTimeHelperTest {
+
+ private static final NetworkTimeSuggestion ARBITRARY_NETWORK_TIME =
+ new NetworkTimeSuggestion(new UnixEpochTime(1234L, 7777L), 123);
+
+ private FakeEnvironment mFakeEnvironment;
+ @Mock private InjectTimeCallback mMockInjectTimeCallback;
+ private TimeDetectorNetworkTimeHelper mTimeDetectorNetworkTimeHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFakeEnvironment = new FakeEnvironment();
+ mTimeDetectorNetworkTimeHelper = new TimeDetectorNetworkTimeHelper(
+ mFakeEnvironment, mMockInjectTimeCallback);
+
+ // TimeDetectorNetworkTimeHelper should register for network time updates during
+ // construction.
+ mFakeEnvironment.assertHasNetworkTimeChangeListener();
+ }
+
+ @Test
+ public void setPeriodicTimeInjectionMode_true() {
+ testSetPeriodicTimeInjectionMode(true);
+ }
+
+ @Test
+ public void setPeriodicTimeInjectionMode_false() {
+ testSetPeriodicTimeInjectionMode(false);
+ }
+
+ private void testSetPeriodicTimeInjectionMode(boolean periodicTimeInjectionMode) {
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ int millisElapsedSinceNetworkTimeReceived = 1000;
+ mFakeEnvironment.pokeLatestNetworkTime(networkTime);
+
+ long currentElapsedRealtimeMillis =
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis()
+ + millisElapsedSinceNetworkTimeReceived;
+ mFakeEnvironment.pokeElapsedRealtimeMillis(currentElapsedRealtimeMillis);
+
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(periodicTimeInjectionMode);
+
+ // All injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // Any call to setPeriodicTimeInjectionMode() should result in an (async) injected time
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // Check whether the scheduled async is set up / not set up for the periodic request.
+ if (periodicTimeInjectionMode) {
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+ } else {
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ }
+ }
+
+ @Test
+ public void periodicInjectionBehavior() {
+ // Set the elapsed realtime clock to an arbitrary start value.
+ mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
+
+ // Configure periodic time injections. Doing so should cause a time query, but no time is
+ // available.
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(true);
+
+ // All query/injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // No time available, so no injection.
+ verifyNoMoreInteractions(mMockInjectTimeCallback);
+
+ // A periodic check should be scheduled.
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+
+ // Time passes...
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
+
+ // A network time becomes available: This should cause the registered listener to trigger.
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.simulateLatestNetworkTimeChange(networkTime);
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ mFakeEnvironment.simulateTimeAdvancing(1);
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // A new periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoImmediateCallback();
+
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+
+ int arbitraryIterationCount = 3;
+ for (int i = 0; i < arbitraryIterationCount; i++) {
+ // Advance by the amount needed for the scheduled work to run. That work should query
+ // and inject.
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS);
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // A new periodic check should be scheduled.
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+ mFakeEnvironment.assertHasNoImmediateCallback();
+ }
+ }
+
+ @Test
+ public void networkTimeAvailableBehavior() {
+ // Set the elapsed realtime clock to an arbitrary start value.
+ mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
+
+ // No periodic time injections. This call causes a time query, but no time is available yet.
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(false);
+
+ // All query/injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // No time available, so no injection.
+ verifyNoMoreInteractions(mMockInjectTimeCallback);
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+
+ // Time passes...
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
+
+ // A network time becomes available: This should cause the registered listener to trigger
+ // and cause time to be injected.
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.simulateLatestNetworkTimeChange(networkTime);
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasNoImmediateCallback();
+ }
+
+ @Test
+ public void networkConnectivityAvailableBehavior() {
+ // Set the elapsed realtime clock to an arbitrary start value.
+ mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
+
+ // No periodic time injections. This call causes a time query, but no time is available yet.
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(false);
+
+ // All query/injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // No time available, so no injection.
+ verifyNoMoreInteractions(mMockInjectTimeCallback);
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+
+ // Time passes...
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
+
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.pokeLatestNetworkTime(networkTime);
+
+ // Simulate location code noticing that connectivity has changed and notifying the helper.
+ mTimeDetectorNetworkTimeHelper.onNetworkAvailable();
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasNoImmediateCallback();
+ }
+
+ @Test
+ public void oldTimesNotInjected() {
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.pokeLatestNetworkTime(networkTime);
+
+ int millisElapsedSinceNetworkTimeReceived = MAX_NETWORK_TIME_AGE_MILLIS;
+ long currentElapsedRealtimeMillis =
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis()
+ + millisElapsedSinceNetworkTimeReceived;
+ mFakeEnvironment.pokeElapsedRealtimeMillis(currentElapsedRealtimeMillis);
+
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(true);
+
+ // All injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+
+ // The age of the network time will now be MAX_NETWORK_TIME_AGE_MILLIS + 1, which is too
+ // old to inject.
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // Old network times should not be injected.
+ verify(mMockInjectTimeCallback, never()).injectTime(anyLong(), anyLong(), anyInt());
+
+ // Check whether the scheduled async is set up / not set up for the periodic request.
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+ }
+
+ /** A fake implementation of {@link Environment} for use by this test. */
+ private static class FakeEnvironment implements Environment {
+
+ private StateChangeListener mNetworkTimeUpdateListener;
+
+ private long mCurrentElapsedRealtimeMillis;
+ private NetworkTimeSuggestion mLatestNetworkTime;
+
+ private TimeDetectorNetworkTimeHelper mImmediateAsyncCallback;
+ private String mImmediateAsyncCallbackReason;
+
+ private TimeDetectorNetworkTimeHelper mScheduledAsyncCallback;
+ private long mScheduledAsyncRunnableTimeMillis;
+
+ @Override
+ public long elapsedRealtimeMillis() {
+ return mCurrentElapsedRealtimeMillis;
+ }
+
+ @Override
+ public NetworkTimeSuggestion getLatestNetworkTime() {
+ return mLatestNetworkTime;
+ }
+
+ @Override
+ public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) {
+ mNetworkTimeUpdateListener = stateChangeListener;
+ }
+
+ @Override
+ public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
+ String reason) {
+ if (mImmediateAsyncCallback != null) {
+ fail("Only one immediate callback expected at a time, found reason: "
+ + mImmediateAsyncCallbackReason);
+ }
+ mImmediateAsyncCallback = helper;
+ mImmediateAsyncCallbackReason = reason;
+ }
+
+ @Override
+ public void requestDelayedTimeQueryCallback(
+ TimeDetectorNetworkTimeHelper instance, long delayMillis) {
+ mScheduledAsyncCallback = instance;
+ mScheduledAsyncRunnableTimeMillis = mCurrentElapsedRealtimeMillis + delayMillis;
+ }
+
+ @Override
+ public void clearDelayedTimeQueryCallback() {
+ mScheduledAsyncCallback = null;
+ mScheduledAsyncRunnableTimeMillis = -1;
+ }
+
+ void pokeLatestNetworkTime(NetworkTimeSuggestion networkTime) {
+ mLatestNetworkTime = networkTime;
+ }
+
+ void pokeElapsedRealtimeMillis(long currentElapsedRealtimeMillis) {
+ mCurrentElapsedRealtimeMillis = currentElapsedRealtimeMillis;
+ }
+
+ void simulateLatestNetworkTimeChange(NetworkTimeSuggestion networkTime) {
+ mLatestNetworkTime = networkTime;
+ mNetworkTimeUpdateListener.onChange();
+ }
+
+ void simulateTimeAdvancing(long durationMillis) {
+ mCurrentElapsedRealtimeMillis += durationMillis;
+
+ if (mImmediateAsyncCallback != null) {
+ TimeDetectorNetworkTimeHelper helper = mImmediateAsyncCallback;
+ String reason = mImmediateAsyncCallbackReason;
+ mImmediateAsyncCallback = null;
+ mImmediateAsyncCallbackReason = null;
+ helper.queryAndInjectNetworkTime(reason);
+ }
+
+ if (mScheduledAsyncCallback != null
+ && mCurrentElapsedRealtimeMillis >= mScheduledAsyncRunnableTimeMillis) {
+ TimeDetectorNetworkTimeHelper helper = mScheduledAsyncCallback;
+ mScheduledAsyncCallback = null;
+ mScheduledAsyncRunnableTimeMillis = -1;
+ helper.delayedQueryAndInjectNetworkTime();
+ }
+ }
+
+ void assertHasNetworkTimeChangeListener() {
+ assertNotNull(mNetworkTimeUpdateListener);
+ }
+
+ void assertHasImmediateCallback() {
+ assertNotNull(mImmediateAsyncCallback);
+ }
+
+ void assertHasNoImmediateCallback() {
+ assertNull(mImmediateAsyncCallback);
+ }
+
+ void assertHasScheduledAsyncCallback(long expectedScheduledAsyncRunnableTimeMillis) {
+ assertNotNull(mScheduledAsyncCallback);
+ assertEquals(expectedScheduledAsyncRunnableTimeMillis,
+ mScheduledAsyncRunnableTimeMillis);
+ }
+
+ void assertHasNoScheduledAsyncCallback() {
+ assertNull(mScheduledAsyncCallback);
+ }
+ }
+}
diff --git a/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java b/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
index 29ad537..c259701 100644
--- a/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
+++ b/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
@@ -140,12 +140,6 @@
}
@Override
- public void requestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {
- runForUserLocked("requestEmptyQueryResultUpdate", sessionId,
- (service) -> service.requestEmptyQueryResultUpdateLocked(sessionId));
- }
-
- @Override
public void destroySearchSession(@NonNull SearchSessionId sessionId) {
runForUserLocked("destroySearchSession", sessionId,
(service) -> service.onDestroyLocked(sessionId));
diff --git a/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java b/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
index 0d70fff..dc150cf 100644
--- a/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
+++ b/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
@@ -181,16 +181,6 @@
}
/**
- * Requests a new set of search targets for empty query result used for zero state.
- */
- @GuardedBy("mLock")
- public void requestEmptyQueryResultUpdateLocked(@NonNull SearchSessionId sessionId) {
- final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
- if (sessionInfo == null) return;
- resolveService(sessionId, s->s.onRequestEmptyQueryResultUpdate(sessionId));
- }
-
- /**
* Notifies the service of the end of an existing search session.
*/
@GuardedBy("mLock")
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index 31c49b3..9c4e6fd 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -33,6 +33,7 @@
"Harrier",
],
platform_apis: true,
+ certificate: "platform",
test_suites: ["device-tests"],
data: [
":AppEnumerationCrossUserPackageVisibilityTestApp",
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/appenumeration/AndroidManifest.xml
index 0395aa8..4749419 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/appenumeration/AndroidManifest.xml
@@ -25,6 +25,7 @@
<!-- It's merged from Harrier library. Remove it since this test should not hold it. -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:node="remove" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.server.pm.test.appenumeration"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
index 31fe184..4012d8e 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
@@ -16,6 +16,8 @@
package com.android.server.pm.test.appenumeration;
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
@@ -26,7 +28,10 @@
import android.content.IntentSender;
import android.content.pm.IPackageManager;
import android.content.pm.ProviderInfo;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.MediaProjectionManager;
import android.os.Process;
+import android.os.ServiceManager;
import android.os.UserHandle;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -160,6 +165,31 @@
null /* onFinished */, null /* handler */));
}
+ @Test
+ public void mediaProjectionManager_createProjection_canSeeForceQueryable()
+ throws Exception {
+ installPackage(SHARED_USER_APK_PATH, true /* forceQueryable */);
+ final IMediaProjectionManager mediaProjectionManager =
+ IMediaProjectionManager.Stub.asInterface(
+ ServiceManager.getService(MEDIA_PROJECTION_SERVICE));
+
+ assertThat(mediaProjectionManager.createProjection(0 /* uid */, TARGET_SHARED_USER,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */))
+ .isNotNull();
+ }
+
+ @Test
+ public void mediaProjectionManager_createProjection_cannotSeeTarget() {
+ installPackage(SHARED_USER_APK_PATH, false /* forceQueryable */);
+ final IMediaProjectionManager mediaProjectionManager =
+ IMediaProjectionManager.Stub.asInterface(
+ ServiceManager.getService(MEDIA_PROJECTION_SERVICE));
+
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mediaProjectionManager.createProjection(0 /* uid */, TARGET_SHARED_USER,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */));
+ }
+
private static void installPackage(String apkPath, boolean forceQueryable) {
final StringBuilder cmd = new StringBuilder("pm install ");
if (forceQueryable) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index d760dc7..811b086 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.server.pm
-import android.Manifest
import android.content.Context
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionParams
@@ -122,12 +121,10 @@
writeRestoreAssert(sessions).single().params.run {
assertThat(legacyGrantedRuntimePermissions).asList()
.containsExactly("grantPermission", "denyToGrantPermission")
- assertThat(finalPermissionStates)
+ assertThat(permissionStates)
.containsExactlyEntriesIn(mapOf(
"grantPermission" to PERMISSION_STATE_GRANTED,
"denyToGrantPermission" to PERMISSION_STATE_GRANTED,
- // Fullscreen Intent is auto-granted if the caller has no opinion
- Manifest.permission.USE_FULL_SCREEN_INTENT to PERMISSION_STATE_GRANTED,
"denyPermission" to PERMISSION_STATE_DENIED,
"grantToDenyPermission" to PERMISSION_STATE_DENIED,
))
@@ -282,7 +279,7 @@
assertThat(expected.referrerUri).isEqualTo(actual.referrerUri)
assertThat(expected.abiOverride).isEqualTo(actual.abiOverride)
assertThat(expected.volumeUuid).isEqualTo(actual.volumeUuid)
- assertThat(expected.finalPermissionStates).isEqualTo(actual.finalPermissionStates)
+ assertThat(expected.permissionStates).isEqualTo(actual.permissionStates)
assertThat(expected.installerPackageName).isEqualTo(actual.installerPackageName)
assertThat(expected.isMultiPackage).isEqualTo(actual.isMultiPackage)
assertThat(expected.isStaged).isEqualTo(actual.isStaged)
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 9895e7c..c7fb32c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -753,7 +753,7 @@
.setPVersionCode(pkg.getLongVersionCode())
.setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
.setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
- .setSharedUserId(pkg.getSharedUserLabelRes())
+ .setSharedUserId(pkg.getSharedUserLabelResourceId())
.build();
}
@@ -763,13 +763,13 @@
assertEquals(a.getBaseRevisionCode(), b.getBaseRevisionCode());
assertEquals(a.isHardwareAccelerated(), b.isHardwareAccelerated());
assertEquals(a.getLongVersionCode(), b.getLongVersionCode());
- assertEquals(a.getSharedUserLabelRes(), b.getSharedUserLabelRes());
+ assertEquals(a.getSharedUserLabelResourceId(), b.getSharedUserLabelResourceId());
assertEquals(a.getInstallLocation(), b.getInstallLocation());
assertEquals(a.isCoreApp(), b.isCoreApp());
assertEquals(a.isRequiredForAllUsers(), b.isRequiredForAllUsers());
assertEquals(a.getCompileSdkVersion(), b.getCompileSdkVersion());
assertEquals(a.getCompileSdkVersionCodeName(), b.getCompileSdkVersionCodeName());
- assertEquals(a.isUse32BitAbi(), b.isUse32BitAbi());
+ assertEquals(a.is32BitAbiPreferred(), b.is32BitAbiPreferred());
assertEquals(a.getPackageName(), b.getPackageName());
assertArrayEquals(a.getSplitNames(), b.getSplitNames());
assertEquals(a.getVolumeUuid(), b.getVolumeUuid());
@@ -1039,7 +1039,7 @@
((ParsedPackage) pkg.setBaseRevisionCode(100)
.setHardwareAccelerated(true)
- .setSharedUserLabelRes(100)
+ .setSharedUserLabelResourceId(100)
.setInstallLocation(100)
.setRequiredForAllUsers(true)
.asSplit(
@@ -1048,7 +1048,7 @@
new int[]{100},
null
)
- .setUse32BitAbi(true)
+ .set32BitAbiPreferred(true)
.setVolumeUuid("d52ef59a-7def-4541-bf21-4c28ed4b65a0")
.addPermission(permission)
.addPermissionGroup(new ParsedPermissionGroupImpl())
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 0b7020c7..6d3cdff 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -155,7 +155,7 @@
AndroidPackage::getAppComponentFactory,
AndroidPackage::getAutoRevokePermissions,
AndroidPackage::getBackupAgentName,
- AndroidPackage::getBannerRes,
+ AndroidPackage::getBannerResourceId,
AndroidPackage::getBaseApkPath,
AndroidPackage::getBaseRevisionCode,
AndroidPackage::getCategory,
@@ -163,16 +163,16 @@
AndroidPackage::getCompatibleWidthLimitDp,
AndroidPackage::getCompileSdkVersion,
AndroidPackage::getCompileSdkVersionCodeName,
- AndroidPackage::getDataExtractionRulesRes,
- AndroidPackage::getDescriptionRes,
- AndroidPackage::getFullBackupContentRes,
+ AndroidPackage::getDataExtractionRulesResourceId,
+ AndroidPackage::getDescriptionResourceId,
+ AndroidPackage::getFullBackupContentResourceId,
AndroidPackage::getGwpAsanMode,
- AndroidPackage::getIconRes,
+ AndroidPackage::getIconResourceId,
AndroidPackage::getInstallLocation,
- AndroidPackage::getLabelRes,
+ AndroidPackage::getLabelResourceId,
AndroidPackage::getLargestWidthLimitDp,
- AndroidPackage::getLogoRes,
- AndroidPackage::getLocaleConfigRes,
+ AndroidPackage::getLogoResourceId,
+ AndroidPackage::getLocaleConfigResourceId,
AndroidPackage::getManageSpaceActivityName,
AndroidPackage::getMaxSdkVersion,
AndroidPackage::getMemtagMode,
@@ -180,7 +180,7 @@
AndroidPackage::getNativeHeapZeroInitialized,
AndroidPackage::getNativeLibraryDir,
AndroidPackage::getNativeLibraryRootDir,
- AndroidPackage::getNetworkSecurityConfigRes,
+ AndroidPackage::getNetworkSecurityConfigResourceId,
AndroidPackage::getNonLocalizedLabel,
AndroidPackage::getOverlayCategory,
AndroidPackage::getOverlayPriority,
@@ -195,11 +195,11 @@
AndroidPackage::getRequiresSmallestWidthDp,
AndroidPackage::getResizeableActivity,
AndroidPackage::getRestrictedAccountType,
- AndroidPackage::getRoundIconRes,
+ AndroidPackage::getRoundIconResourceId,
PackageImpl::getSecondaryCpuAbi,
AndroidPackage::getSecondaryNativeLibraryDir,
AndroidPackage::getSharedUserId,
- AndroidPackage::getSharedUserLabelRes,
+ AndroidPackage::getSharedUserLabelResourceId,
AndroidPackage::getSdkLibraryName,
AndroidPackage::getSdkLibVersionMajor,
AndroidPackage::getStaticSharedLibraryName,
@@ -207,21 +207,21 @@
AndroidPackage::getTargetSandboxVersion,
AndroidPackage::getTargetSdkVersion,
AndroidPackage::getTaskAffinity,
- AndroidPackage::getThemeRes,
+ AndroidPackage::getThemeResourceId,
AndroidPackage::getUiOptions,
AndroidPackage::getUid,
AndroidPackage::getVersionName,
AndroidPackage::getZygotePreloadName,
AndroidPackage::isAllowAudioPlaybackCapture,
- AndroidPackage::isAllowBackup,
- AndroidPackage::isAllowClearUserData,
- AndroidPackage::isAllowClearUserDataOnFailedRestore,
+ AndroidPackage::isBackupAllowed,
+ AndroidPackage::isClearUserDataAllowed,
+ AndroidPackage::isClearUserDataOnFailedRestoreAllowed,
AndroidPackage::isAllowNativeHeapPointerTagging,
- AndroidPackage::isAllowTaskReparenting,
+ AndroidPackage::isTaskReparentingAllowed,
AndroidPackage::isAllowUpdateOwnership,
AndroidPackage::isBackupInForeground,
AndroidPackage::isHardwareAccelerated,
- AndroidPackage::isCantSaveState,
+ AndroidPackage::isSaveStateDisallowed,
AndroidPackage::isCoreApp,
AndroidPackage::isCrossProfile,
AndroidPackage::isDebuggable,
@@ -229,17 +229,17 @@
AndroidPackage::isDirectBootAware,
AndroidPackage::isEnabled,
AndroidPackage::isExternalStorage,
- AndroidPackage::isExtractNativeLibs,
+ AndroidPackage::isExtractNativeLibrariesRequested,
AndroidPackage::isFactoryTest,
AndroidPackage::isApex,
AndroidPackage::isForceQueryable,
AndroidPackage::isFullBackupOnly,
AndroidPackage::isGame,
- AndroidPackage::isHasCode,
+ AndroidPackage::isDeclaredHavingCode,
AndroidPackage::isHasDomainUrls,
- AndroidPackage::isHasFragileUserData,
+ AndroidPackage::isUserDataFragile,
AndroidPackage::isIsolatedSplitLoading,
- AndroidPackage::isKillAfterRestore,
+ AndroidPackage::isKillAfterRestoreAllowed,
AndroidPackage::isLargeHeap,
AndroidPackage::isMultiArch,
AndroidPackage::isNativeLibraryRootRequiresIsa,
@@ -257,12 +257,12 @@
AndroidPackage::isSdkLibrary,
AndroidPackage::isStaticSharedLibrary,
AndroidPackage::isStub,
- AndroidPackage::isSupportsRtl,
+ AndroidPackage::isRtlSupported,
AndroidPackage::isTestOnly,
- AndroidPackage::isUse32BitAbi,
+ AndroidPackage::is32BitAbiPreferred,
AndroidPackage::isUseEmbeddedDex,
- AndroidPackage::isUsesCleartextTraffic,
- AndroidPackage::isUsesNonSdkApi,
+ AndroidPackage::isCleartextTrafficAllowed,
+ AndroidPackage::isNonSdkApiRequested,
AndroidPackage::isVisibleToInstantApps,
AndroidPackage::isVmSafeMode,
AndroidPackage::isLeavingSharedUser,
@@ -282,10 +282,10 @@
getter(AndroidPackage::getUpgradeKeySets, setOf("testUpgradeKeySet")),
getter(AndroidPackage::isAnyDensity, false, 0),
getter(AndroidPackage::isResizeable, false, 0),
- getter(AndroidPackage::isSupportsSmallScreens, false, 0),
- getter(AndroidPackage::isSupportsNormalScreens, false, 0),
- getter(AndroidPackage::isSupportsLargeScreens, false, 0),
- getter(AndroidPackage::isSupportsExtraLargeScreens, false, 0),
+ getter(AndroidPackage::isSmallScreensSupported, false, 0),
+ getter(AndroidPackage::isNormalScreensSupported, false, 0),
+ getter(AndroidPackage::isLargeScreensSupported, false, 0),
+ getter(AndroidPackage::isExtraLargeScreensSupported, false, 0),
adder(AndroidPackage::getAdoptPermissions, "test.adopt.PERMISSION"),
adder(AndroidPackage::getOriginalPackages, "com.test.original"),
adder(AndroidPackage::getImplicitPermissions, "test.implicit.PERMISSION"),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index 8a855e5..5a733c7 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -312,4 +312,4 @@
.that(exception)
.isNotNull()
}
-}
\ No newline at end of file
+}
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 419351d4..9263bff 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/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index ad224df5..68cfe45 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -223,8 +223,7 @@
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
BroadcastRecord record, int recordIndex, long enqueueTime) {
- queue.enqueueOrReplaceBroadcast(record, recordIndex,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(record, recordIndex, false);
record.enqueueTime = enqueueTime;
}
@@ -354,8 +353,7 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
queue.setProcessCached(false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -377,14 +375,12 @@
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
- queue.enqueueOrReplaceBroadcast(timezoneRecord, 0,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -415,8 +411,7 @@
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 1,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -439,8 +434,7 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -466,13 +460,11 @@
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(lazyRecord, 0,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false);
assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
- queue.enqueueOrReplaceBroadcast(testRecord, 0,
- null /* replacedBroadcastConsumer */, false);
+ queue.enqueueOrReplaceBroadcast(testRecord, 0, false);
assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
}
@@ -534,26 +526,20 @@
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
- null /* replacedBroadcastConsumer */, false);
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0,
- null /* replacedBroadcastConsumer */, false);
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
- null /* replacedBroadcastConsumer */, false);
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
- null /* replacedBroadcastConsumer */, false);
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0,
- null /* replacedBroadcastConsumer */, false);
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
- null /* replacedBroadcastConsumer */, false);
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 458c9cfc..6bc2d1f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -20,6 +20,7 @@
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -76,8 +77,6 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -160,7 +159,7 @@
private ActivityManagerService mAms;
private BroadcastQueue mQueue;
BroadcastConstants mConstants;
- private TestBroadcastSkipPolicy mSkipPolicy;
+ private BroadcastSkipPolicy mSkipPolicy;
/**
* Desired behavior of the next
@@ -287,7 +286,10 @@
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mConstants.TIMEOUT = 100;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
- mSkipPolicy = new TestBroadcastSkipPolicy(mAms);
+
+ mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
+ doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
+ doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
@@ -324,48 +326,6 @@
}
}
- private static class TestBroadcastSkipPolicy extends BroadcastSkipPolicy {
- private final ArrayMap<String, ArraySet> mReceiversToSkip = new ArrayMap<>();
-
- TestBroadcastSkipPolicy(ActivityManagerService service) {
- super(service);
- }
-
- public String shouldSkipMessage(BroadcastRecord r, Object o) {
- if (shouldSkipReceiver(r.intent.getAction(), o)) {
- return "test skipped receiver";
- }
- return null;
- }
-
- private boolean shouldSkipReceiver(String action, Object o) {
- final ArraySet<Object> receiversToSkip = mReceiversToSkip.get(action);
- if (receiversToSkip == null) {
- return false;
- }
- for (int i = 0; i < receiversToSkip.size(); ++i) {
- if (BroadcastRecord.isReceiverEquals(o, receiversToSkip.valueAt(i))) {
- return true;
- }
- }
- return false;
- }
-
- public void setSkipReceiver(String action, Object o) {
- ArraySet<Object> receiversToSkip = mReceiversToSkip.get(action);
- if (receiversToSkip == null) {
- receiversToSkip = new ArraySet<>();
- mReceiversToSkip.put(action, receiversToSkip);
- }
- receiversToSkip.add(o);
- }
- public boolean disallowBackgroundStart(BroadcastRecord r) {
- // Ignored
- return false;
- }
-
- }
-
private class TestInjector extends Injector {
TestInjector(Context context) {
super(context);
@@ -2039,8 +1999,16 @@
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver)));
- mSkipPolicy.setSkipReceiver(airplane.getAction(), greenReceiver);
- mSkipPolicy.setSkipReceiver(airplane.getAction(), orangeReceiver);
+ doAnswer(invocation -> {
+ final BroadcastRecord r = invocation.getArgument(0);
+ final Object o = invocation.getArgument(1);
+ if (airplane.getAction().equals(r.intent.getAction())
+ && (isReceiverEquals(o, greenReceiver)
+ || isReceiverEquals(o, orangeReceiver))) {
+ return "test skipped receiver";
+ }
+ return null;
+ }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any());
}
waitForIdle();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 4524759..95a5884 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -69,6 +69,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@SmallTest
@@ -692,7 +694,10 @@
// Verify that committed triggered a new change event and is set correctly.
verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
- assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+ // We expect at least 1 update for the state change, but
+ // could get a second update for the initial brightness change if a nits mapping
+ // is available
+ assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2);
assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo(
Display.STATE_OFF);
@@ -964,6 +969,43 @@
.isTrue();
}
+ @Test
+ public void testHdrSdrRatio_notifiesOnChange() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f);
+
+ // HDR time!
+ Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
+ 0);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // Display state didn't change, no listeners should have happened
+ assertThat(mListener.changedDisplays.size()).isEqualTo(0);
+
+ // Execute hdr change.
+ goHdrRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // Display state didn't change, expect to only get the HDR/SDR ratio change notification
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0];
+ assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio,
+ 0.001f);
+ }
+
private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
float expectedXdpi,
float expectedYDpi,
@@ -1107,15 +1149,9 @@
private static void waitForHandlerToComplete(Handler handler, long waitTimeMs)
throws InterruptedException {
- final Object lock = new Object();
- synchronized (lock) {
- handler.post(() -> {
- synchronized (lock) {
- lock.notify();
- }
- });
- lock.wait(waitTimeMs);
- }
+ final CountDownLatch fence = new CountDownLatch(1);
+ handler.post(fence::countDown);
+ assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS));
}
private class HotplugTransmitter {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 3750aa0..2fbbf0d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -49,6 +49,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.BackgroundStartPrivileges;
import android.app.IActivityManager;
import android.app.job.JobInfo;
import android.content.ComponentName;
@@ -63,6 +64,7 @@
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.SparseIntArray;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -178,6 +180,8 @@
mGracePeriodObserver = mock(GracePeriodObserver.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ doReturn(BackgroundStartPrivileges.NONE)
+ .when(mActivityManagerInternal).getBackgroundStartPrivileges(anyInt());
mDefaultUserId = mNextUserId;
createCurrentUser(true);
mNextUserId = 10;
@@ -601,34 +605,75 @@
@Test
public void testHasImmediacyPrivilege() {
- JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
+ final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE;
+ JobStatus job = createJob(uid, 0);
spyOn(job);
- assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ doReturn(BackgroundStartPrivileges.NONE)
+ .when(mActivityManagerInternal).getBackgroundStartPrivileges(uid);
+
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
doReturn(false).when(job).shouldTreatAsExpeditedJob();
doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
- assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
doReturn(true).when(job).shouldTreatAsExpeditedJob();
doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
- assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
doReturn(false).when(job).shouldTreatAsExpeditedJob();
doReturn(true).when(job).shouldTreatAsUserInitiatedJob();
job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
- assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
doReturn(false).when(job).shouldTreatAsExpeditedJob();
doReturn(true).when(job).shouldTreatAsUserInitiatedJob();
job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
- assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
doReturn(true).when(job).shouldTreatAsExpeditedJob();
doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
- assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job,
+ new SparseIntArray()));
+
+ doReturn(BackgroundStartPrivileges.ALLOW_FGS)
+ .when(mActivityManagerInternal).getBackgroundStartPrivileges(uid);
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
+
+ doReturn(true).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(true).when(job).shouldTreatAsUserInitiatedJob();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
+
+ doReturn(BackgroundStartPrivileges.ALLOW_BAL)
+ .when(mActivityManagerInternal).getBackgroundStartPrivileges(uid);
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
+
+ doReturn(true).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiatedJob();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(true).when(job).shouldTreatAsUserInitiatedJob();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job, new SparseIntArray()));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index b9893f6..f1acf66 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -147,12 +147,12 @@
TEST_PACKAGE_NAME,
1L,
rule.system().dataAppDirectory,
- withPackage = { it.apply { isHasCode = true } })
+ withPackage = { it.apply { isDeclaredHavingCode = true } })
rule.system().stageScanExistingPackage(
TEST_PACKAGE_2_NAME,
1L,
rule.system().dataAppDirectory,
- withPackage = { it.apply { isHasCode = true } })
+ withPackage = { it.apply { isDeclaredHavingCode = true } })
val pm = createPackageManagerService()
rule.system().validateFinalState()
whenever(appHibernationManager.isHibernatingGlobally(TEST_PACKAGE_2_NAME)).thenReturn(true)
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 76dfe02..89a5b12 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -708,7 +708,8 @@
public void testStartProfile_fullUserFails() {
setUpUser(TEST_USER_ID1, 0);
assertThrows(IllegalArgumentException.class,
- () -> mUserController.startProfile(TEST_USER_ID1));
+ () -> mUserController.startProfile(TEST_USER_ID1, /* evenWhenDisabled= */ false,
+ /* unlockListener= */ null));
verifyUserNeverAssignedToDisplay();
}
@@ -725,7 +726,8 @@
public void testStartProfile_disabledProfileFails() {
setUpUser(TEST_USER_ID1, UserInfo.FLAG_PROFILE | UserInfo.FLAG_DISABLED, /* preCreated= */
false, UserManager.USER_TYPE_PROFILE_MANAGED);
- assertThat(mUserController.startProfile(TEST_USER_ID1)).isFalse();
+ assertThat(mUserController.startProfile(TEST_USER_ID1, /* evenWhenDisabled=*/ false,
+ /* unlockListener= */ null)).isFalse();
verifyUserNeverAssignedToDisplay();
}
@@ -906,7 +908,8 @@
private void setUpAndStartProfileInBackground(int userId) throws Exception {
setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED);
- assertThat(mUserController.startProfile(userId)).isTrue();
+ assertThat(mUserController.startProfile(userId, /* evenWhenDisabled=*/ false,
+ /* unlockListener= */ null)).isTrue();
verify(mInjector.mLockPatternUtilsMock, times(1)).unlockUserKeyIfUnsecured(userId);
mUserStates.put(userId, mUserController.getStartedUserState(userId));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index e605a31..99f7905 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -35,7 +35,6 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
-import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
@@ -365,16 +364,6 @@
showHideOverlay(c -> c.onLockoutPermanent());
}
- @Test
- public void testPowerPressForwardsErrorMessage() throws RemoteException {
- final FingerprintAuthenticationClient client = createClient();
-
- client.onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR,
- BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED);
-
- verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
- eq(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED), eq(0));
- }
private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
throws RemoteException {
final FingerprintAuthenticationClient client = createClient();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index a40d3fe..26524d7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -27,7 +29,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -275,12 +276,11 @@
@Test
public void testPowerPressForwardsAcquireMessage() throws RemoteException {
final FingerprintEnrollClient client = createClient();
-
- client.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR,
- BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED);
+ client.start(mCallback);
+ client.onPowerPressed();
verify(mClientMonitorCallbackConverter).onAcquired(anyInt(),
- eq(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED), eq(0));
+ eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt());
}
private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index ef8a49f..6431e88 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.hardware.Sensor;
@@ -54,6 +55,8 @@
@Mock
private SensorManagerInternal mSensorManagerInternalMock;
+ @Mock
+ private IVirtualSensorCallback mVirtualSensorCallback;
private SensorController mSensorController;
private VirtualSensorEvent mSensorEvent;
private VirtualSensorConfig mVirtualSensorConfig;
@@ -66,7 +69,8 @@
LocalServices.removeServiceForTest(SensorManagerInternal.class);
LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
- mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+ mSensorController =
+ new SensorController(new Object(), VIRTUAL_DEVICE_ID, mVirtualSensorCallback);
mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
mVirtualSensorConfig =
new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
@@ -135,6 +139,7 @@
private void doCreateSensorSuccessfully() {
doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
anyInt(), anyInt(), anyString(), anyString(), any());
- mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+ assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig))
+ .isEqualTo(SENSOR_HANDLE);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ce5ddb0..0cd50f0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -55,6 +55,9 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.IVirtualSensorCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
@@ -102,6 +105,7 @@
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
@@ -225,6 +229,10 @@
@Mock
private SensorManagerInternal mSensorManagerInternalMock;
@Mock
+ private IVirtualSensorCallback mVirtualSensorCallback;
+ @Mock
+ private VirtualSensorCallback mSensorCallback;
+ @Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
private IVirtualDeviceSoundEffectListener mSoundEffectListener;
@@ -333,7 +341,8 @@
mInputController = new InputController(new Object(), mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
- mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID_1);
+ mSensorController =
+ new SensorController(new Object(), VIRTUAL_DEVICE_ID_1, mVirtualSensorCallback);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
@@ -451,6 +460,42 @@
}
@Test
+ public void getVirtualSensor_defaultDeviceId_returnsNull() {
+ assertThat(mLocalService.getVirtualSensor(DEVICE_ID_DEFAULT, SENSOR_HANDLE)).isNull();
+ }
+
+ @Test
+ public void getVirtualSensor_invalidDeviceId_returnsNull() {
+ assertThat(mLocalService.getVirtualSensor(DEVICE_ID_INVALID, SENSOR_HANDLE)).isNull();
+ }
+
+ @Test
+ public void getVirtualSensor_noSensors_returnsNull() {
+ assertThat(mLocalService.getVirtualSensor(VIRTUAL_DEVICE_ID_1, SENSOR_HANDLE)).isNull();
+ }
+
+ @Test
+ public void getVirtualSensor_returnsCorrectSensor() {
+ VirtualDeviceParams params = new VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .addVirtualSensorConfig(
+ new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, DEVICE_NAME_1)
+ .build())
+ .setVirtualSensorCallback(BackgroundThread.getExecutor(), mSensorCallback)
+ .build();
+
+ doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+
+ VirtualSensor sensor = mLocalService.getVirtualSensor(VIRTUAL_DEVICE_ID_1, SENSOR_HANDLE);
+ assertThat(sensor).isNotNull();
+ assertThat(sensor.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID_1);
+ assertThat(sensor.getHandle()).isEqualTo(SENSOR_HANDLE);
+ assertThat(sensor.getType()).isEqualTo(Sensor.TYPE_ACCELEROMETER);
+ }
+
+ @Test
public void getDeviceIdsForUid_noRunningApps_returnsNull() {
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).isEmpty();
@@ -888,18 +933,6 @@
}
@Test
- public void createVirtualSensor_noPermission_failsSecurityException() {
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualSensor(
- BINDER,
- new VirtualSensorConfig.Builder(
- Sensor.TYPE_ACCELEROMETER, DEVICE_NAME_1).build()));
- }
- }
-
- @Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
deleted file mode 100644
index 798650d..0000000
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.virtual;
-
-import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
-import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
-import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
-import static android.hardware.Sensor.TYPE_ACCELEROMETER;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.companion.virtual.VirtualDeviceParams;
-import android.companion.virtual.sensor.VirtualSensorConfig;
-import android.os.Parcel;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.Set;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualDeviceParamsTest {
-
- private static final String SENSOR_NAME = "VirtualSensorName";
- private static final String SENSOR_VENDOR = "VirtualSensorVendor";
- private static final int PLAYBACK_SESSION_ID = 42;
- private static final int RECORDING_SESSION_ID = 77;
-
- @Test
- public void parcelable_shouldRecreateSuccessfully() {
- VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
- .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
- .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
- .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
- .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
- .setAudioPlaybackSessionId(PLAYBACK_SESSION_ID)
- .setAudioRecordingSessionId(RECORDING_SESSION_ID)
- .addVirtualSensorConfig(
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setVendor(SENSOR_VENDOR)
- .build())
- .build();
- Parcel parcel = Parcel.obtain();
- originalParams.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
-
- VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel);
- assertThat(params).isEqualTo(originalParams);
- assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
- assertThat(params.getUsersWithMatchingAccounts())
- .containsExactly(UserHandle.of(123), UserHandle.of(456));
- assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
- assertThat(params.getDevicePolicy(POLICY_TYPE_AUDIO)).isEqualTo(DEVICE_POLICY_CUSTOM);
- assertThat(params.getAudioPlaybackSessionId()).isEqualTo(PLAYBACK_SESSION_ID);
- assertThat(params.getAudioRecordingSessionId()).isEqualTo(RECORDING_SESSION_ID);
-
- List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
- assertThat(sensorConfigs).hasSize(1);
- VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
- assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
- assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
- assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
- assertThat(sensorConfig.getStateChangeCallback()).isNull();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index b5c5582..120ddf6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -49,6 +49,7 @@
import com.android.server.SystemService;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -156,6 +157,7 @@
@SmallTest
@Test
+ @Ignore("b/269249457")
public void testCompMigrationAffiliated() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 1904671..a616fbc 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -183,6 +183,10 @@
int[] getSupportedHdrOutputTypes() {
return new int[]{};
}
+
+ boolean getHdrOutputConversionSupport() {
+ return false;
+ }
}
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
index 704b06b..87aa272 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
@@ -24,6 +24,8 @@
import android.app.timedetector.TelephonyTimeSuggestion;
import android.util.IndentingPrintWriter;
+import com.android.server.timezonedetector.StateChangeListener;
+
/**
* A fake implementation of {@link com.android.server.timedetector.TimeDetectorStrategy} for use
* in tests.
@@ -31,6 +33,7 @@
public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
// State
private TimeState mTimeState;
+ private NetworkTimeSuggestion mLatestNetworkTimeSuggestion;
@Override
public TimeState getTimeState() {
@@ -62,8 +65,12 @@
}
@Override
+ public void addNetworkTimeUpdateListener(StateChangeListener networkSuggestionUpdateListener) {
+ }
+
+ @Override
public NetworkTimeSuggestion getLatestNetworkSuggestion() {
- return null;
+ return mLatestNetworkTimeSuggestion;
}
@Override
@@ -81,4 +88,8 @@
@Override
public void dump(IndentingPrintWriter pw, String[] args) {
}
+
+ void setLatestNetworkTime(NetworkTimeSuggestion networkTimeSuggestion) {
+ mLatestNetworkTimeSuggestion = networkTimeSuggestion;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 0b339ad..5a0867f 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -54,6 +54,7 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper;
import com.android.server.timezonedetector.TestCallerIdentityInjector;
import com.android.server.timezonedetector.TestHandler;
@@ -420,21 +421,35 @@
@Test
public void testGetLatestNetworkSuggestion() {
- NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
- 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
- when(mMockNtpTrustedTime.getCachedTimeResult())
- .thenReturn(latestNetworkTime);
- UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
- latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
- NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
- expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
- assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion();
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime);
+
+ assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion());
+ } else {
+ NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
+ 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
+ when(mMockNtpTrustedTime.getCachedTimeResult())
+ .thenReturn(latestNetworkTime);
+ UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+ latestNetworkTime.getElapsedRealtimeMillis(),
+ latestNetworkTime.getTimeMillis());
+ NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
+ expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
+ assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ }
}
@Test
public void testGetLatestNetworkSuggestion_noTimeAvailable() {
- when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
- assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
+
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ } else {
+ when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 37da2a2..4df21e0 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -41,6 +41,7 @@
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.StateChangeListener;
+import com.android.server.timezonedetector.TestStateChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +52,8 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import junitparams.JUnitParamsRunner;
@@ -863,8 +866,11 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
.setOriginPriorities(ORIGIN_NETWORK)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
NetworkTimeSuggestion timeSuggestion =
script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -877,6 +883,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -885,7 +896,10 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
.setOriginPriorities(ORIGIN_NETWORK)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
NetworkTimeSuggestion timeSuggestion =
script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -894,6 +908,11 @@
.simulateNetworkTimeSuggestion(timeSuggestion)
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -902,7 +921,11 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
.setOriginPriorities(ORIGIN_NETWORK, ORIGIN_EXTERNAL)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal);
+
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
// Create two different time suggestions for the current elapsedRealtimeMillis.
ExternalTimeSuggestion externalTimeSuggestion =
@@ -927,6 +950,11 @@
script.simulateNetworkTimeSuggestion(networkTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
// Clear the network time. This should cause the device to change back to the external time,
@@ -937,6 +965,11 @@
script.simulateClearLatestNetworkSuggestion()
.assertLatestNetworkSuggestion(null)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+
+ // Confirm network time update listeners are asynchronously notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
}
@@ -947,8 +980,11 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -957,6 +993,11 @@
.assertLatestNetworkSuggestion(null)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is not notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
}
@Test
@@ -966,8 +1007,10 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
.build();
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -976,6 +1019,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -985,8 +1033,10 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
.build();
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -995,6 +1045,11 @@
.assertLatestNetworkSuggestion(null)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is not notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
}
@Test
@@ -1004,8 +1059,10 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
.build();
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -1014,6 +1071,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -1800,7 +1862,10 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
.setOriginPriorities(ORIGIN_TELEPHONY)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion(
ARBITRARY_TEST_TIME);
@@ -1809,6 +1874,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -1867,6 +1937,8 @@
*/
private static class FakeEnvironment implements TimeDetectorStrategyImpl.Environment {
+ private final List<Runnable> mAsyncRunnables = new ArrayList<>();
+
private ConfigurationInternal mConfigurationInternal;
private boolean mWakeLockAcquired;
private long mElapsedRealtimeMillis;
@@ -1951,8 +2023,20 @@
// No-op for tests
}
+ @Override
+ public void runAsync(Runnable runnable) {
+ mAsyncRunnables.add(runnable);
+ }
+
// Methods below are for managing the fake's behavior.
+ void runAsyncRunnables() {
+ for (Runnable runnable : mAsyncRunnables) {
+ runnable.run();
+ }
+ mAsyncRunnables.clear();
+ }
+
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
mConfigurationInternalChangeListener.onChange();
@@ -1992,7 +2076,7 @@
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
+ void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
assertEquals(expectedConfidence, mSystemClockConfidence);
}
@@ -2118,6 +2202,17 @@
return this;
}
+ /** Calls {@link TimeDetectorStrategy#addNetworkTimeUpdateListener(StateChangeListener)}. */
+ Script addNetworkTimeUpdateListener(StateChangeListener listener) {
+ mTimeDetectorStrategy.addNetworkTimeUpdateListener(listener);
+ return this;
+ }
+
+ Script simulateAsyncRunnableExecution() {
+ mFakeEnvironment.runAsyncRunnables();
+ return this;
+ }
+
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeEnvironment.verifySystemClockNotSet();
mFakeEnvironment.resetCallTracking();
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 0d6bb8a..566c6b0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -570,7 +570,7 @@
.setAutoDetectionEnabledSetting(false)
.build();
assertFalse(config.getAutoDetectionEnabledBehavior());
- assertTrue(config.isGeoDetectionExecutionEnabled());
+ assertFalse(config.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestStateChangeListener.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestStateChangeListener.java
new file mode 100644
index 0000000..9cbf0a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestStateChangeListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestStateChangeListener implements StateChangeListener {
+
+ private int mNotificationsReceived;
+
+ @Override
+ public void onChange() {
+ mNotificationsReceived++;
+ }
+
+ public void assertNotificationsReceivedAndReset(int expectedCount) {
+ assertNotificationsReceived(expectedCount);
+ resetNotificationsReceivedCount();
+ }
+
+ private void resetNotificationsReceivedCount() {
+ mNotificationsReceived = 0;
+ }
+
+ private void assertNotificationsReceived(int expectedCount) {
+ assertEquals(expectedCount, mNotificationsReceived);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 590aba9..03d406f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -2001,26 +2001,4 @@
return new TelephonyTestCase(matchType, quality, expectedScore);
}
- private static class TestStateChangeListener implements StateChangeListener {
-
- private int mNotificationsReceived;
-
- @Override
- public void onChange() {
- mNotificationsReceived++;
- }
-
- public void assertNotificationsReceivedAndReset(int expectedCount) {
- assertNotificationsReceived(expectedCount);
- resetNotificationsReceivedCount();
- }
-
- private void resetNotificationsReceivedCount() {
- mNotificationsReceived = 0;
- }
-
- private void assertNotificationsReceived(int expectedCount) {
- assertEquals(expectedCount, mNotificationsReceived);
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index 7b1db95..aeb8ec8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -24,6 +24,7 @@
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
+import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_TELEPHONY;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
@@ -1385,6 +1386,37 @@
}
/**
+ * A controller-state-only test to prove that "run in background" doesn't enable the
+ * location-based time zone detection algorithm to run when auto detection is disabled.
+ */
+ @Test
+ public void geoDetectionRunInBackground_obeysAutoDetectionDisabled() {
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */);
+
+ // A configuration where the user has auto detection disabled.
+ ConfigurationInternal autoTimeZoneDisabledConfig =
+ new ConfigurationInternal.Builder(USER1_CONFIG_GEO_DETECTION_DISABLED)
+ .setLocationEnabledSetting(true)
+ .setAutoDetectionEnabledSetting(false)
+ .setGeoDetectionEnabledSetting(true)
+ .setGeoDetectionRunInBackgroundEnabled(true)
+ .build();
+ assertEquals(DETECTION_MODE_MANUAL, autoTimeZoneDisabledConfig.getDetectionMode());
+ assertFalse(autoTimeZoneDisabledConfig.isGeoDetectionExecutionEnabled());
+
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controller, autoTimeZoneDisabledConfig);
+
+ // Initialize and check initial state.
+ controller.initialize(testEnvironment, mTestCallback);
+
+ assertControllerState(controller, STATE_STOPPED);
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
+ }
+
+ /**
* A controller-state-only test to prove that "run in background" configuration behaves as
* intended. Provider states are well covered by other "enabled" tests.
*/
@@ -1398,7 +1430,7 @@
ConfigurationInternal runInBackgroundDisabledConfig =
new ConfigurationInternal.Builder(USER1_CONFIG_GEO_DETECTION_DISABLED)
.setLocationEnabledSetting(true)
- .setAutoDetectionEnabledSetting(false)
+ .setAutoDetectionEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
.setGeoDetectionRunInBackgroundEnabled(false)
.build();
@@ -1408,7 +1440,7 @@
new ConfigurationInternal.Builder(runInBackgroundDisabledConfig)
.setGeoDetectionRunInBackgroundEnabled(true)
.build();
- assertEquals(DETECTION_MODE_MANUAL, runInBackgroundEnabledConfig.getDetectionMode());
+ assertEquals(DETECTION_MODE_TELEPHONY, runInBackgroundEnabledConfig.getDetectionMode());
assertTrue(runInBackgroundEnabledConfig.isGeoDetectionExecutionEnabled());
TestEnvironment testEnvironment = new TestEnvironment(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 9c68ddc..33ca5c2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -302,30 +302,6 @@
}
@Test
- public void testXmlMigratingAllowedAdjustments() throws Exception {
- // Old tag, need migration
- String xml = "<q_allowed_adjustments types=\"adj_1\"/>";
-
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(xml.toString().getBytes())), null);
- parser.nextTag();
- mAssistants.readExtraTag("q_allowed_adjustments", parser);
- assertTrue(mAssistants.isAdjustmentAllowed("adj_1"));
- assertEquals(mNm.DEFAULT_ALLOWED_ADJUSTMENTS.length + 1,
- mAssistants.getAllowedAssistantAdjustments().size());
-
- // New TAG
- xml = "<s_allowed_adjustments types=\"adj_2\"/>";
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(xml.toString().getBytes())), null);
- parser.nextTag();
- mAssistants.readExtraTag("s_allowed_adjustments", parser);
- assertTrue(mAssistants.isAdjustmentAllowed("adj_2"));
- assertEquals(1, mAssistants.getAllowedAssistantAdjustments().size());
- }
-
- @Test
public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 69fccf2..687696a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -203,7 +203,7 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.internal.messages.nano.SystemMessageProto;
@@ -377,7 +377,7 @@
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
- TestFlagResolver mTestFlagResolver = new TestFlagResolver();
+ TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@@ -7619,17 +7619,8 @@
@Test
public void testGetAllowedAssistantAdjustments() throws Exception {
- List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
- assertNotNull(capabilities);
-
- for (int i = capabilities.size() - 1; i >= 0; i--) {
- String capability = capabilities.get(i);
- mBinderService.disallowAssistantAdjustment(capability);
- assertEquals(i + 1, mBinderService.getAllowedAssistantAdjustments(null).size());
- List<String> currentCapabilities = mBinderService.getAllowedAssistantAdjustments(null);
- assertNotNull(currentCapabilities);
- assertFalse(currentCapabilities.contains(capability));
- }
+ List<String> adjustments = mBinderService.getAllowedAssistantAdjustments(null);
+ assertNotNull(adjustments);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 6f2627a..a3977ba 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -64,6 +64,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.server.LocalServices;
@@ -168,7 +169,7 @@
mock(ActivityManagerInternal.class),
mock(MultiRateLimiter.class), mock(PermissionHelper.class),
mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
- mock(NotificationChannelLogger.class), new TestFlagResolver());
+ mock(NotificationChannelLogger.class), new TestableFlagResolver());
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 3344bdb..1d0715a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -222,7 +222,7 @@
}
@Override
- public void onKeyguardOccludedChangedLw(boolean occluded) {
+ public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) {
}
public void setSafeMode(boolean safeMode) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index cdbcecc..05ee2f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -704,15 +704,15 @@
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
- final Task newTask = createTask(mDisplayContent);
- doReturn(false).when(newTask).isTranslucent(any());
final Task oldTask = createTask(mDisplayContent);
- doReturn(false).when(oldTask).isTranslucent(any());
+ final Task newTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
+ closing.visibleIgnoringKeyguard = true;
final ActivityRecord opening = createActivityRecord(newTask);
- opening.setOccludesParent(false);
+ opening.setOccludesParent(true);
+ opening.visibleIgnoringKeyguard = true;
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
@@ -735,8 +735,8 @@
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
- assertTrue((info.getChanges().get(0).getFlags() & FLAG_TRANSLUCENT) == 0);
- assertTrue((info.getChanges().get(1).getFlags() & FLAG_TRANSLUCENT) == 0);
+ assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
@@ -745,15 +745,15 @@
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
- final Task newTask = createTask(mDisplayContent);
- doReturn(true).when(newTask).isTranslucent(any());
final Task oldTask = createTask(mDisplayContent);
- doReturn(false).when(oldTask).isTranslucent(any());
+ final Task newTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
+ closing.visibleIgnoringKeyguard = true;
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
+ opening.visibleIgnoringKeyguard = true;
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
@@ -776,8 +776,186 @@
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
- assertTrue((info.getChanges().get(0).getFlags() & FLAG_TRANSLUCENT) != 0);
- assertTrue((info.getChanges().get(1).getFlags() & FLAG_TRANSLUCENT) == 0);
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
+ }
+
+ @Test
+ public void testOpenOpaqueTaskFragment() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
+ final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
+
+ final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
+ closing.setOccludesParent(true);
+ closing.visibleIgnoringKeyguard = true;
+ final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
+ opening.setOccludesParent(true);
+ opening.visibleIgnoringKeyguard = true;
+ // Start states.
+ changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
+ false /* vis */, true /* exChg */));
+ changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
+ true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
+ fillChangeMap(changes, openingTaskFragment);
+ // End states.
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
+
+ final int transit = transition.mType;
+ int flags = 0;
+
+ // Check basic both tasks participating
+ participants.add(closingTaskFragment);
+ participants.add(openingTaskFragment);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transit, info.getType());
+
+ assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
+ }
+
+ @Test
+ public void testOpenTranslucentTaskFragment() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
+ final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
+
+ final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
+ closing.setOccludesParent(true);
+ closing.visibleIgnoringKeyguard = true;
+ final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
+ opening.setOccludesParent(false);
+ opening.visibleIgnoringKeyguard = true;
+ // Start states.
+ changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
+ false /* vis */, true /* exChg */));
+ changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
+ true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
+ fillChangeMap(changes, openingTaskFragment);
+ // End states.
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
+
+ final int transit = transition.mType;
+ int flags = 0;
+
+ // Check basic both tasks participating
+ participants.add(closingTaskFragment);
+ participants.add(openingTaskFragment);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transit, info.getType());
+
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
+ }
+
+ @Test
+ public void testCloseOpaqueTaskFragment_withFinishingActivity() {
+ final Transition transition = createTestTransition(TRANSIT_CLOSE);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
+ final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
+
+ final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
+ opening.setOccludesParent(true);
+ opening.visibleIgnoringKeyguard = true;
+ final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
+ closing.setOccludesParent(true);
+ closing.visibleIgnoringKeyguard = true;
+ closing.finishing = true;
+ // Start states.
+ changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
+ false /* vis */, true /* exChg */));
+ changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
+ true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
+ fillChangeMap(changes, openingTaskFragment);
+ // End states.
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
+
+ final int transit = transition.mType;
+ int flags = 0;
+
+ // Check basic both tasks participating
+ participants.add(closingTaskFragment);
+ participants.add(openingTaskFragment);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transit, info.getType());
+
+ assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
+ }
+
+ @Test
+ public void testCloseTranslucentTaskFragment_withFinishingActivity() {
+ final Transition transition = createTestTransition(TRANSIT_CLOSE);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
+ final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
+
+ final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
+ opening.setOccludesParent(true);
+ opening.visibleIgnoringKeyguard = true;
+ final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
+ closing.setOccludesParent(false);
+ closing.visibleIgnoringKeyguard = true;
+ closing.finishing = true;
+ // Start states.
+ changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
+ false /* vis */, true /* exChg */));
+ changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
+ true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
+ fillChangeMap(changes, openingTaskFragment);
+ // End states.
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
+
+ final int transit = transition.mType;
+ int flags = 0;
+
+ // Check basic both tasks participating
+ participants.add(closingTaskFragment);
+ participants.add(openingTaskFragment);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transit, info.getType());
+
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index f3c91f6..2135e27 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -197,10 +197,10 @@
* of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
- * rejected the incoming call.
+ * started the call streaming.
*
* {@link OutcomeReceiver#onError} will be called if Telecom has failed to
- * reject the incoming call. A {@link CallException} will be passed that
+ * start the call streaming. A {@link CallException} will be passed that
* details why the operation failed.
*/
public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
index 7f11128..3f538a7 100644
--- a/telecomm/java/android/telecom/CallStreamingService.java
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
@@ -38,10 +39,22 @@
/**
* This service is implemented by an app that wishes to provide functionality for a general call
* streaming sender for voip calls.
- *
- * TODO: add doc of how to be the general streaming sender
- *
+ * <p>
+ * Below is an example manifest registration for a {@code CallStreamingService}.
+ * <pre>
+ * {@code
+ * <service android:name=".EgCallStreamingService"
+ * android:permission="android.permission.BIND_CALL_STREAMING_SERVICE" >
+ * ...
+ * <intent-filter>
+ * <action android:name="android.telecom.CallStreamingService" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ * @hide
*/
+@SystemApi
public abstract class CallStreamingService extends Service {
/**
* The {@link android.content.Intent} that must be declared as handled by the service.
@@ -122,6 +135,13 @@
/**
* Call streaming request reject reason used with
* {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request due to unknown reason.
+ */
+ public static final int STREAMING_FAILED_UNKNOWN = 0;
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
* call streaming request because there's an ongoing streaming call on this device.
*/
public static final int STREAMING_FAILED_ALREADY_STREAMING = 1;
@@ -149,6 +169,7 @@
*/
@IntDef(prefix = {"STREAMING_FAILED"},
value = {
+ STREAMING_FAILED_UNKNOWN,
STREAMING_FAILED_ALREADY_STREAMING,
STREAMING_FAILED_NO_SENDER,
STREAMING_FAILED_SENDER_BINDING_ERROR
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
index 4b27dd6..d4f4322 100644
--- a/telecomm/java/android/telecom/StreamingCall.java
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
@@ -30,7 +31,10 @@
/**
* Represents a voip call requested to stream to another device that the general streaming sender
* app should present to the receiver.
+ *
+ * @hide
*/
+@SystemApi
public final class StreamingCall implements Parcelable {
/**
* The state of a {@code StreamingCall} when newly created. General streaming sender should
@@ -50,9 +54,12 @@
*/
public static final int STATE_DISCONNECTED = 3;
+ /**
+ * @hide
+ */
private StreamingCall(@NonNull Parcel in) {
mComponentName = in.readParcelable(ComponentName.class.getClassLoader());
- mDisplayName = in.readString16NoHelper();
+ mDisplayName = in.readCharSequence();
mAddress = in.readParcelable(Uri.class.getClassLoader());
mExtras = in.readBundle();
mState = in.readInt();
@@ -79,7 +86,7 @@
@Override
public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
dest.writeParcelable(mComponentName, flags);
- dest.writeString16NoHelper(mDisplayName);
+ dest.writeCharSequence(mDisplayName);
dest.writeParcelable(mAddress, flags);
dest.writeBundle(mExtras);
dest.writeInt(mState);
@@ -98,14 +105,14 @@
public @interface StreamingCallState {}
private final ComponentName mComponentName;
- private final String mDisplayName;
+ private final CharSequence mDisplayName;
private final Uri mAddress;
private final Bundle mExtras;
@StreamingCallState
private int mState;
private StreamingCallAdapter mAdapter = null;
- public StreamingCall(@NonNull ComponentName componentName, @NonNull String displayName,
+ public StreamingCall(@NonNull ComponentName componentName, @NonNull CharSequence displayName,
@NonNull Uri address, @NonNull Bundle extras) {
mComponentName = componentName;
mDisplayName = displayName;
@@ -137,7 +144,7 @@
* {@code StreamingCall} to the receiver side.
*/
@NonNull
- public String getDisplayName() {
+ public CharSequence getDisplayName() {
return mDisplayName;
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 7404adb..d75e573 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -174,7 +174,7 @@
/**
* Key to the backup & restore data byte array in the Bundle that is returned by {@link
* #getAllSimSpecificSettingsForBackup()} or to be pass in to {@link
- * #restoreAllSimSpecificSettings()}.
+ * #restoreAllSimSpecificSettingsFromBackup(byte[])}.
*
* @hide
*/
@@ -4051,39 +4051,16 @@
}
/**
- * Called to attempt to restore the backed up sim-specific configs to device for specific sim.
- * This will try to restore the data that was stored internally when {@link
- * #restoreAllSimSpecificSettingsFromBackup(byte[] data)} was called during setup wizard.
- * End result is SimInfoDB is modified to match any backed up configs for the requested
- * inserted sim.
- *
- * <p>
- * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any SimInfoDB
- * entry is updated as the result of this method call.
- *
- * @param iccId of the sim that a restore is requested for.
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
- public void restoreSimSpecificSettingsForIccIdFromBackup(@NonNull String iccId) {
- mContext.getContentResolver().call(
- SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
- RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
- iccId, null);
- }
-
- /**
* Called during setup wizard restore flow to attempt to restore the backed up sim-specific
- * configs to device for all existing SIMs in SimInfoDB. Internally, it will store the backup
- * data in an internal file. This file will persist on device for device's lifetime and will be
- * used later on when a SIM is inserted to restore that specific SIM's settings by calling
- * {@link #restoreSimSpecificSettingsForIccIdFromBackup(String iccId)}. End result is
- * SimInfoDB is modified to match any backed up configs for the appropriate inserted SIMs.
+ * configs to device for all existing SIMs in the subscription database {@link SimInfo}.
+ * Internally, it will store the backup data in an internal file. This file will persist on
+ * device for device's lifetime and will be used later on when a SIM is inserted to restore that
+ * specific SIM's settings. End result is subscription database is modified to match any backed
+ * up configs for the appropriate inserted SIMs.
*
* <p>
- * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any SimInfoDB
- * entry is updated as the result of this method call.
+ * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any
+ * {@link SimInfo} entry is updated as the result of this method call.
*
* @param data with the sim specific configs to be backed up.
*
@@ -4092,12 +4069,18 @@
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[] data) {
- Bundle bundle = new Bundle();
- bundle.putByteArray(KEY_SIM_SPECIFIC_SETTINGS_DATA, data);
- mContext.getContentResolver().call(
- SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
- RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
- null, bundle);
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.restoreAllSimSpecificSettingsFromBackup(data);
+ } else {
+ throw new IllegalStateException("subscription service unavailable.");
+ }
+ } catch (RemoteException ex) {
+ if (!isSystemProcess()) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
}
/**
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl
index 6fb0979..cc4c543 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl
@@ -17,13 +17,18 @@
package android.telephony.satellite;
import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteDatagram;
/**
* Interface for satellite state listener.
* @hide
*/
oneway interface ISatelliteStateListener {
- void onSatelliteProvisionStateChanged(in int[] features, in boolean provisioned);
+ void onSatelliteProvisionStateChanged(in boolean provisioned);
void onSatellitePositionUpdate(in PointingInfo pointingInfo);
- void onMessageTransferStateUpdate(in int state);
+ void onMessageTransferStateUpdate(in int state, in int sendPendingCount,
+ in int receivePendingCount, in int errorCode);
+ void onSatelliteModemStateChange(in int state);
+ void onPendingMessageCount(in int count);
+ void onSatelliteDatagrams(in SatelliteDatagram[] datagrams);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCallback.java b/telephony/java/android/telephony/satellite/SatelliteCallback.java
index 1b82d06..7f24a2f 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCallback.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.os.Binder;
-import android.telephony.satellite.stub.SatelliteImplBase;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
@@ -63,16 +62,14 @@
/**
* Called when satellite provision state changes.
*
- * @param features The list of provisioned features.
* @param provisioned The new provision state. {@code true} means satellite is provisioned
* {@code false} means satellite is not provisioned.
*/
- void onSatelliteProvisionStateChanged(
- @SatelliteImplBase.Feature int[] features, boolean provisioned);
+ void onSatelliteProvisionStateChanged(boolean provisioned);
}
/**
- * Interface for position update change listener.
+ * Interface for position update and message transfer state change listener.
*/
public interface SatellitePositionUpdateListener {
/**
@@ -86,9 +83,41 @@
* Called when satellite message transfer state changes.
*
* @param state The new message transfer state.
+ * @param sendPendingCount The number of messages that are currently being sent.
+ * @param receivePendingCount The number of messages that are currently being received.
+ * @param errorCode If message transfer failed, the reason for failure.
*/
void onMessageTransferStateUpdate(
- @SatelliteManager.SatelliteMessageTransferState int state);
+ @SatelliteManager.SatelliteMessageTransferState int state, int sendPendingCount,
+ int receivePendingCount, @SatelliteManager.SatelliteError int errorCode);
+ }
+
+ /**
+ * Interface for satellite state change listener.
+ */
+ public interface SatelliteStateListener {
+ /**
+ * Called when satellite state changes.
+ * @param state The new satellite state.
+ */
+ void onSatelliteModemStateChange(@SatelliteManager.SatelliteModemState int state);
+
+ /**
+ * Called when there are pending messages to be received from satellite.
+ * @param count Pending message count.
+ */
+ void onPendingMessageCount(int count);
+ }
+
+ /**
+ * Interface for satellite datagram listener.
+ */
+ public interface SatelliteDatagramListener {
+ /**
+ * Called when there are incoming datagrams to be received.
+ * @param datagrams Datagrams to be received over satellite.
+ */
+ void onSatelliteDatagrams(SatelliteDatagram[] datagrams);
}
private static class ISatelliteStateListenerStub extends ISatelliteStateListener.Stub {
@@ -100,14 +129,13 @@
mExecutor = executor;
}
- public void onSatelliteProvisionStateChanged(
- @SatelliteImplBase.Feature int[] features, boolean provisioned) {
+ public void onSatelliteProvisionStateChanged(boolean provisioned) {
SatelliteProvisionStateListener listener =
(SatelliteProvisionStateListener) mSatelliteCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> listener.onSatelliteProvisionStateChanged(features, provisioned)));
+ () -> listener.onSatelliteProvisionStateChanged(provisioned)));
}
public void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo) {
@@ -120,13 +148,46 @@
}
public void onMessageTransferStateUpdate(
- @SatelliteManager.SatelliteMessageTransferState int state) {
+ @SatelliteManager.SatelliteMessageTransferState int state, int sendPendingCount,
+ int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
SatellitePositionUpdateListener listener =
(SatellitePositionUpdateListener) mSatelliteCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> listener.onMessageTransferStateUpdate(state)));
+ () -> listener.onMessageTransferStateUpdate(
+ state, sendPendingCount, receivePendingCount, errorCode)));
+ }
+
+
+ @Override
+ public void onSatelliteModemStateChange(@SatelliteManager.SatelliteModemState int state) {
+ SatelliteStateListener listener =
+ (SatelliteStateListener) mSatelliteCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onSatelliteModemStateChange(state)));
+ }
+
+ @Override
+ public void onPendingMessageCount(int count) {
+ SatelliteStateListener listener =
+ (SatelliteStateListener) mSatelliteCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onPendingMessageCount(count)));
+ }
+
+ @Override
+ public void onSatelliteDatagrams(SatelliteDatagram[] datagrams) {
+ SatelliteDatagramListener listener =
+ (SatelliteDatagramListener) mSatelliteCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onSatelliteDatagrams(datagrams)));
}
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index c5ae4db..74f6f57 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -33,8 +33,8 @@
private Set<Integer> mSupportedRadioTechnologies;
/**
- * Whether satellite mode is always on (this to indicate power impact of keeping it on is
- * very minimal).
+ * Whether satellite modem is always on.
+ * This indicates the power impact of keeping it on is very minimal.
*/
private boolean mIsAlwaysOn;
@@ -44,12 +44,7 @@
private boolean mNeedsPointingToSatellite;
/**
- * List of features supported by the Satellite modem.
- */
- private Set<Integer> mSupportedFeatures;
-
- /**
- * Whether UE needs a separate SIM profile to communicate with the Satellite network.
+ * Whether UE needs a separate SIM profile to communicate with the satellite network.
*/
private boolean mNeedsSeparateSimProfile;
@@ -57,12 +52,10 @@
* @hide
*/
public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
- boolean needsPointingToSatellite, Set<Integer> supportedFeatures,
- boolean needsSeparateSimProfile) {
+ boolean needsPointingToSatellite, boolean needsSeparateSimProfile) {
mSupportedRadioTechnologies = supportedRadioTechnologies;
mIsAlwaysOn = isAlwaysOn;
mNeedsPointingToSatellite = needsPointingToSatellite;
- mSupportedFeatures = supportedFeatures;
mNeedsSeparateSimProfile = needsSeparateSimProfile;
}
@@ -88,35 +81,23 @@
out.writeBoolean(mIsAlwaysOn);
out.writeBoolean(mNeedsPointingToSatellite);
-
- if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
- out.writeInt(mSupportedFeatures.size());
- for (int feature : mSupportedFeatures) {
- out.writeInt(feature);
- }
- } else {
- out.writeInt(0);
- }
-
out.writeBoolean(mNeedsSeparateSimProfile);
}
- public static final @android.annotation.NonNull Creator<SatelliteCapabilities> CREATOR =
- new Creator<SatelliteCapabilities>() {
- @Override
- public SatelliteCapabilities createFromParcel(Parcel in) {
- return new SatelliteCapabilities(in);
- }
+ @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
+ @Override
+ public SatelliteCapabilities createFromParcel(Parcel in) {
+ return new SatelliteCapabilities(in);
+ }
- @Override
- public SatelliteCapabilities[] newArray(int size) {
- return new SatelliteCapabilities[size];
- }
- };
+ @Override
+ public SatelliteCapabilities[] newArray(int size) {
+ return new SatelliteCapabilities[size];
+ }
+ };
- @NonNull
@Override
- public String toString() {
+ @NonNull public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SupportedRadioTechnology:");
@@ -129,16 +110,6 @@
sb.append("none,");
}
- sb.append("SupportedFeatures:");
- if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
- for (int feature : mSupportedFeatures) {
- sb.append(feature);
- sb.append(",");
- }
- } else {
- sb.append("none,");
- }
-
sb.append("isAlwaysOn:");
sb.append(mIsAlwaysOn);
sb.append(",");
@@ -152,26 +123,39 @@
return sb.toString();
}
- @NonNull
- public Set<Integer> getSupportedRadioTechnologies() {
+ /**
+ * @return The list of technologies supported by the satellite modem.
+ */
+ @NonNull public Set<Integer> getSupportedRadioTechnologies() {
return mSupportedRadioTechnologies;
}
+ /**
+ * Get whether the satellite modem is always on.
+ * This indicates the power impact of keeping it on is very minimal.
+ *
+ * @return {@code true} if the satellite modem is always on and {@code false} otherwise.
+ */
public boolean isAlwaysOn() {
return mIsAlwaysOn;
}
- /** Get function for mNeedsPointingToSatellite */
+ /**
+ * Get whether UE needs to point to a satellite to send and receive data.
+ *
+ * @return {@code true} if UE needs to pointing to a satellite to send and receive data and
+ * {@code false} otherwise.
+ */
public boolean needsPointingToSatellite() {
return mNeedsPointingToSatellite;
}
- @NonNull
- public Set<Integer> getSupportedFeatures() {
- return mSupportedFeatures;
- }
-
- /** Get function for mNeedsSeparateSimProfile */
+ /**
+ * Get whether UE needs a separate SIM profile to communicate with the satellite network.
+ *
+ * @return {@code true} if UE needs a separate SIM profile to comunicate with the satellite
+ * network and {@code false} otherwise.
+ */
public boolean needsSeparateSimProfile() {
return mNeedsSeparateSimProfile;
}
@@ -187,15 +171,6 @@
mIsAlwaysOn = in.readBoolean();
mNeedsPointingToSatellite = in.readBoolean();
-
- mSupportedFeatures = new HashSet<>();
- int numSupportedFeatures = in.readInt();
- if (numSupportedFeatures > 0) {
- for (int i = 0; i < numSupportedFeatures; i++) {
- mSupportedFeatures.add(in.readInt());
- }
- }
-
mNeedsSeparateSimProfile = in.readBoolean();
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.aidl b/telephony/java/android/telephony/satellite/SatelliteDatagram.aidl
new file mode 100644
index 0000000..993aacf
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony.satellite;
+
+parcelable SatelliteDatagram;
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
new file mode 100644
index 0000000..cc5a9f4
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -0,0 +1,75 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SatelliteDatagram implements Parcelable {
+ /**
+ * Datagram to be sent or received over satellite.
+ */
+ private byte[] mData;
+
+ /**
+ * @hide
+ */
+ public SatelliteDatagram(@NonNull byte[] data) {
+ mData = data;
+ }
+
+ private SatelliteDatagram(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeByteArray(mData);
+ }
+
+ public static final @android.annotation.NonNull Creator<SatelliteDatagram> CREATOR =
+ new Creator<SatelliteDatagram>() {
+ @Override
+ public SatelliteDatagram createFromParcel(Parcel in) {
+ return new SatelliteDatagram(in);
+ }
+
+ @Override
+ public SatelliteDatagram[] newArray(int size) {
+ return new SatelliteDatagram[size];
+ }
+ };
+
+ @Nullable
+ public byte[] getSatelliteDatagram() {
+ return mData;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mData = in.createByteArray();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 129d4ca..9ce4310 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -144,6 +144,21 @@
public static final String KEY_SATELLITE_PROVISIONED = "satellite_provisioned";
/**
+ * Bundle key to get the response from
+ * {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_SATELLITE_COMMUNICATION_ALLOWED =
+ "satellite_communication_allowed";
+
+ /**
+ * Bundle key to get the response from
+ * {@link #requestTimeForNextSatelliteVisibility(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
+
+ /**
* The request was successfully processed.
*/
public static final int SATELLITE_ERROR_NONE = 0;
@@ -294,7 +309,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- Rlog.e(TAG, "setSatelliteEnabled RemoteException: ", ex);
+ Rlog.e(TAG, "setSatelliteEnabled() RemoteException: ", ex);
ex.rethrowFromSystemServer();
}
}
@@ -460,42 +475,97 @@
}
/**
- * Message transfer is waiting to acquire.
+ * The default state indicating that message transfer is idle.
+ * This should be sent immediately after either
+ * {@link #SATELLITE_MESSAGE_TRANSFER_STATE_SUCCESS} or
+ * {@link #SATELLITE_MESSAGE_TRANSFER_STATE_FAILED}.
*/
- public static final int SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE = 0;
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_IDLE = 0;
/**
- * Message is being sent.
+ * A transition state indicating that a message is being sent.
*/
public static final int SATELLITE_MESSAGE_TRANSFER_STATE_SENDING = 1;
/**
- * Message is being received.
+ * A transition state indicating that a message is being received.
*/
public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING = 2;
/**
- * Message transfer is being retried.
+ * A transition state indicating that message transfer is being retried.
*/
public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING = 3;
/**
- * Message transfer is complete.
+ * An end state indicating that message transfer completed successfully.
+ * After message transfer completes, {@link #SATELLITE_MESSAGE_TRANSFER_STATE_IDLE}
+ * must be sent before reporting any additional message transfer state changes.
*/
- public static final int SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE = 4;
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_SUCCESS = 4;
+ /**
+ * An end state indicating that message transfer completed with a failure.
+ * After message transfer completes, {@link #SATELLITE_MESSAGE_TRANSFER_STATE_IDLE}
+ * must be sent before reporting any additional message transfer state changes.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_FAILED = 5;
/** @hide */
@IntDef(prefix = {"SATELLITE_MESSAGE_TRANSFER_STATE_"}, value = {
- SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE,
+ SATELLITE_MESSAGE_TRANSFER_STATE_IDLE,
SATELLITE_MESSAGE_TRANSFER_STATE_SENDING,
SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING,
SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING,
- SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE
+ SATELLITE_MESSAGE_TRANSFER_STATE_SUCCESS,
+ SATELLITE_MESSAGE_TRANSFER_STATE_FAILED
})
public @interface SatelliteMessageTransferState {}
+ /* Satellite modem is in idle state. */
+ public static final int SATELLITE_MODEM_STATE_IDLE = 0;
+
+ /* Satellite modem is listening for incoming messages. */
+ public static final int SATELLITE_MODEM_STATE_LISTENING = 1;
+
+ /* Satellite modem is sending and/or receiving messages. */
+ public static final int SATELLITE_MODEM_STATE_MESSAGE_TRANSFERRING = 2;
+
+ /* Satellite modem is powered off */
+ public static final int SATELLITE_MODEM_STATE_OFF = 3;
+
+ /** @hide */
+ @IntDef(prefix = {"SATELLITE_STATE_"},
+ value = {
+ SATELLITE_MODEM_STATE_IDLE,
+ SATELLITE_MODEM_STATE_LISTENING,
+ SATELLITE_MODEM_STATE_MESSAGE_TRANSFERRING,
+ SATELLITE_MODEM_STATE_OFF
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteModemState {}
+
+ /** SOS SMS */
+ public static final int DATAGRAM_TYPE_SOS_SMS = 0;
+
+ /** Location sharing message */
+ public static final int DATAGRAM_TYPE_LOCATION_SHARING = 3;
+
+ @IntDef(
+ prefix = "DATAGRAM_TYPE_",
+ value = {
+ DATAGRAM_TYPE_SOS_SMS,
+ DATAGRAM_TYPE_LOCATION_SHARING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DatagramType {}
+
/**
* Start receiving satellite position updates.
* This can be called by the pointing UI when the user starts pointing to the satellite.
* Modem should continue to report the pointing input as the device or satellite moves.
* Satellite position updates are started only on {@link #SATELLITE_ERROR_NONE}.
* All other results indicate that this operation failed.
+ * Once satellite position updates begin, message transfer state updates will be sent
+ * through {@link SatelliteCallback.SatellitePositionUpdateListener}.
+ * Modem should report any changes in message transfer state and indicate success or failure
+ * by reporting {@link #SATELLITE_MESSAGE_TRANSFER_STATE_SUCCESS} or
+ * {@link #SATELLITE_MESSAGE_TRANSFER_STATE_FAILED}.
*
* @param executor The executor on which the callback and error code listener will be called.
* @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
@@ -530,7 +600,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("startSatellitePositionUpdates RemoteException: " + ex);
+ loge("startSatellitePositionUpdates() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
}
@@ -538,8 +608,8 @@
/**
* Stop receiving satellite position updates.
* This can be called by the pointing UI when the user stops pointing to the satellite.
- * Satellite position updates are stopped only on {@link #SATELLITE_ERROR_NONE}.
- * All other results indicate that this operation failed.
+ * Satellite position updates are stopped and the callback is unregistered only on
+ * {@link #SATELLITE_ERROR_NONE}. All other results that this operation failed.
*
* @param callback The callback that was passed to
* {@link #startSatellitePositionUpdates(Executor, Consumer, SatelliteCallback)}.
@@ -547,7 +617,6 @@
* @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalArgumentException if the callback is invalid.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -575,7 +644,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("stopSatellitePositionUpdates RemoteException: " + ex);
+ loge("stopSatellitePositionUpdates() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
}
@@ -633,12 +702,12 @@
}
}
-
/**
* Provision the device with a satellite provider.
* This is needed if the provider allows dynamic registration.
*
- * @param token The security token of the device/subscription to be provisioned.
+ * @param token The token to be used as a unique identifier for provisioning with satellite
+ * gateway.
* @param cancellationSignal The optional signal used by the caller to cancel the provision
* request. Even when the cancellation is signaled, Telephony will
* still trigger the callback to return the result of this request.
@@ -673,7 +742,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("provisionSatelliteService RemoteException=" + ex);
+ loge("provisionSatelliteService() RemoteException=" + ex);
ex.rethrowFromSystemServer();
}
if (cancellationSignal != null) {
@@ -682,92 +751,116 @@
}
/**
- * Register for the satellite provision state change.
+ * Deprovision the device with the satellite provider.
+ * This is needed if the provider allows dynamic registration. Once deprovisioned,
+ * {@link SatelliteCallback.SatelliteProvisionStateListener#onSatelliteProvisionStateChanged}
+ * should report as deprovisioned.
+ * For provisioning satellite service, refer to
+ * {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}.
*
- * @param executor The executor on which the callback and error code listener will be called.
+ * @param token The token of the device/subscription to be deprovisioned.
* @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
- * @param callback The callback to handle the satellite provision state changed event. This
- * SatelliteCallback should implement the interface
- * {@link SatelliteCallback.SatelliteProvisionStateListener}.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void registerForSatelliteProvisionStateChanged(
+ public void deprovisionSatelliteService(@NonNull String token,
@NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<Integer> errorCodeListener, @NonNull SatelliteCallback callback) {
+ @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+ Objects.requireNonNull(token);
Objects.requireNonNull(executor);
Objects.requireNonNull(errorCodeListener);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> errorCodeListener.accept(result)));
+ }
+ };
+ telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("deprovisionSatelliteService() RemoteException=" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register for the satellite provision state change.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the satellite provision state changed event.
+ * This SatelliteCallback should implement the interface
+ * {@link SatelliteCallback.SatelliteProvisionStateListener}.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteError public int registerForSatelliteProvisionStateChanged(
+ @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteCallback callback) {
+ Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
callback.init(executor);
- IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
- @Override
- public void accept(int result) {
- executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
- }
- };
- telephony.registerForSatelliteProvisionStateChanged(
- mSubId, errorCallback, callback.getCallbackStub());
+ return telephony.registerForSatelliteProvisionStateChanged(
+ mSubId, callback.getCallbackStub());
} else {
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("registerForSatelliteProvisionStateChanged RemoteException: " + ex);
+ loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
+ return SATELLITE_REQUEST_FAILED;
}
/**
* Unregister for the satellite provision state change.
*
- * @param callback The callback that was passed to
- * {@link #registerForSatelliteProvisionStateChanged(Executor, Consumer, SatelliteCallback)}.
- * @param executor The executor on which the error code listener will be called.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+ * @param callback The callback that was passed to {@link
+ * #registerForSatelliteProvisionStateChanged(Executor, SatelliteCallback)}.
+ *
+ * @return The {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void unregisterForSatelliteProvisionStateChanged(@NonNull SatelliteCallback callback,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<Integer> errorCodeListener) {
+ @SatelliteError public int unregisterForSatelliteProvisionStateChanged(
+ @NonNull SatelliteCallback callback) {
Objects.requireNonNull(callback);
- Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
if (callback.getCallbackStub() == null) {
loge("unregisterForSatelliteProvisionStateChanged: callbackStub is null");
- executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(SATELLITE_INVALID_ARGUMENTS)));
- return;
+ return SATELLITE_INVALID_ARGUMENTS;
}
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
- @Override
- public void accept(int result) {
- executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
- }
- };
- telephony.unregisterForSatelliteProvisionStateChanged(mSubId, errorCallback,
+ return telephony.unregisterForSatelliteProvisionStateChanged(mSubId,
callback.getCallbackStub());
} else {
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("unregisterForSatelliteProvisionStateChanged RemoteException: " + ex);
+ loge("unregisterForSatelliteProvisionStateChanged() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
+ return SATELLITE_REQUEST_FAILED;
}
/**
@@ -824,6 +917,322 @@
}
}
+ /**
+ * Register for listening to satellite modem state changes.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the satellite state change event.
+ * This SatelliteCallback should implement the interface
+ * {@link SatelliteCallback.SatelliteStateListener}.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteError public int registerForSatelliteModemStateChange(
+ @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ callback.init(executor);
+ return telephony.registerForSatelliteModemStateChange(mSubId,
+ callback.getCallbackStub());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForSatelliteModemStateChange() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregister to stop listening to satellite modem state changes.
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForSatelliteModemStateChange(Executor, SatelliteCallback)}.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteError
+ public int unregisterForSatelliteModemStateChange(@NonNull SatelliteCallback callback) {
+ Objects.requireNonNull(callback);
+
+ if (callback.getCallbackStub() == null) {
+ loge("unregisterForSatelliteModemStateChange: callbackStub is null");
+ return SATELLITE_INVALID_ARGUMENTS;
+ }
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.unregisterForSatelliteModemStateChange(mSubId,
+ callback.getCallbackStub());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForSatelliteModemStateChange() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_REQUEST_FAILED;
+ }
+
+ /**
+ * Register to receive incoming datagrams over satellite.
+ *
+ * @param datagramType Type of datagram.
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle incoming datagrams over satellite.
+ * This SatelliteCallback should implement the interface
+ * {@link SatelliteCallback.SatelliteDatagramListener}.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteError public int registerForSatelliteDatagram(@DatagramType int datagramType,
+ @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ callback.init(executor);
+ return telephony.registerForSatelliteDatagram(mSubId, datagramType,
+ callback.getCallbackStub());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForSatelliteDatagram() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregister to stop receiving incoming datagrams over satellite.
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForSatelliteDatagram(int, Executor, SatelliteCallback)}.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteError public int unregisterForSatelliteDatagram(@NonNull SatelliteCallback callback) {
+ Objects.requireNonNull(callback);
+
+ if (callback.getCallbackStub() == null) {
+ loge("unregisterForSatelliteDatagram: callbackStub is null");
+ return SATELLITE_INVALID_ARGUMENTS;
+ }
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.unregisterForSatelliteDatagram(mSubId, callback.getCallbackStub());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForSatelliteDatagram() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_REQUEST_FAILED;
+ }
+
+ /**
+ * Poll pending satellite datagrams over satellite.
+ *
+ * @return The result of the operation.
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteError public int pollPendingSatelliteDatagrams() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.pollPendingSatelliteDatagrams(mSubId);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("pollPendingSatelliteDatagrams() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_REQUEST_FAILED;
+ }
+
+ /**
+ * Send datagram over satellite.
+ *
+ * @param datagramType Type of datagram.
+ * @param datagram Datagram to send over satellite.
+ * @param executor The executor on which the error code listener will be called.
+ * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void sendSatelliteDatagram(@DatagramType int datagramType,
+ @NonNull SatelliteDatagram datagram, @NonNull @CallbackExecutor Executor executor,
+ @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+ Objects.requireNonNull(datagram);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(errorCodeListener);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> errorCodeListener.accept(result)));
+ }
+ };
+ telephony.sendSatelliteDatagram(mSubId, datagramType, datagram, internalCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("sendSatelliteDatagram() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request to get whether satellite communication is allowed for the current location.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return a {@code boolean} with value {@code true} if satellite
+ * communication is allowed for the current location and
+ * {@code false} otherwise.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+ * will return a {@link SatelliteException} with the {@link SatelliteError}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_ERROR_NONE) {
+ if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+ boolean isSatelliteCommunicationAllowed =
+ resultData.getBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isSatelliteCommunicationAllowed)));
+ } else {
+ loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(
+ new SatelliteException(SATELLITE_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestIsSatelliteCommunicationAllowedForCurrentLocation(mSubId,
+ receiver);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() RemoteException: "
+ + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request to get the time after which the satellite will next be visible. This is an
+ * {@code int} representing the duration in seconds after which the satellite will be visible.
+ * This will return {@code 0} if the satellite is currently visible.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return the time after which the satellite will next be visible.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+ * will return a {@link SatelliteException} with the {@link SatelliteError}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_ERROR_NONE) {
+ if (resultData.containsKey(KEY_SATELLITE_NEXT_VISIBILITY)) {
+ int nextVisibilityDuration =
+ resultData.getInt(KEY_SATELLITE_NEXT_VISIBILITY);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(nextVisibilityDuration)));
+ } else {
+ loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(
+ new SatelliteException(SATELLITE_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index defa046..af4edc4 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -363,4 +363,20 @@
*/
//TODO: Removed before U AOSP public release.
boolean isSubscriptionManagerServiceEnabled();
+
+ /**
+ * Called during setup wizard restore flow to attempt to restore the backed up sim-specific
+ * configs to device for all existing SIMs in the subscription database
+ * {@link Telephony.SimInfo}. Internally, it will store the backup data in an internal file.
+ * This file will persist on device for device's lifetime and will be used later on when a SIM
+ * is inserted to restore that specific SIM's settings. End result is subscription database is
+ * modified to match any backed up configs for the appropriate inserted SIMs.
+ *
+ * <p>
+ * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any
+ * {@link Telephony.SimInfo} entry is updated as the result of this method call.
+ *
+ * @param data with the sim specific configs to be backed up.
+ */
+ void restoreAllSimSpecificSettingsFromBackup(in byte[] data);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 07deead..ec8dafb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -69,6 +69,7 @@
import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.satellite.ISatelliteStateListener;
import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
@@ -2770,7 +2771,7 @@
/**
* Request to get the maximum number of characters per text message on satellite.
*
- * @param subId The subId to get the maximum number of characters for.
+ * @param subId The subId of the subscription to get the maximum number of characters for.
* @param receiver Result receiver to get the error code of the request and the requested
* maximum number of characters per text message on satellite.
*/
@@ -2783,7 +2784,8 @@
* This is needed to register the subscription if the provider allows dynamic registration.
*
* @param subId The subId of the subscription to be provisioned.
- * @param token The security token of the device/subscription to be provisioned.
+ * @param token The token to be used as a unique identifier for provisioning with satellite
+ * gateway.
* @param callback The callback to get the error code of the request.
*
* @return The signal transport used by callers to cancel the provision request.
@@ -2794,28 +2796,44 @@
in IIntegerConsumer callback);
/**
- * Register for the satellite provision state change.
+ * Unregister the subscription with the satellite provider.
+ * This is needed to unregister the subscription if the provider allows dynamic registration.
+ * Once deprovisioned,
+ * {@link SatelliteCallback.SatelliteProvisionStateListener#onSatelliteProvisionStateChanged}
+ * should report as deprovisioned.
*
- * @param subId The subId of the subscription to register for provision state changes for.
- * @param errorCallback The callback to get the error code of the request.
- * @param callback The callback to handle the satellite provision state changed event.
+ * @param subId The subId of the subscription to be deprovisioned.
+ * @param token The token of the device/subscription to be deprovisioned.
+ * @param callback The callback to get the error code of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void registerForSatelliteProvisionStateChanged(int subId,
- in IIntegerConsumer errorCallback, in ISatelliteStateListener callback);
+ void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
+
+
+ /**
+ * Register for the satellite provision state change.
+ *
+ * @param subId The subId of the subscription to register for provision state changes.
+ * @param callback The callback to handle the satellite provision state changed event.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForSatelliteProvisionStateChanged(int subId, in ISatelliteStateListener callback);
/**
* Unregister for the satellite provision state change.
*
- * @param subId The subId of the subscription associated with the satellite service.
- * @param errorCallback The callback to get the error code of the request.
+ * @param subId The subId of the subscription to unregister for provision state changes.
* @param callback The callback that was passed to registerForSatelliteProvisionStateChanged.
+ *
+ * @return The {@link SatelliteError} result of the operation.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForSatelliteProvisionStateChanged(int subId,
- in IIntegerConsumer errorCallback, in ISatelliteStateListener callback);
+ int unregisterForSatelliteProvisionStateChanged(int subId, in ISatelliteStateListener callback);
/**
* Request to get whether the device is provisioned with a satellite provider.
@@ -2827,4 +2845,101 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void requestIsSatelliteProvisioned(int subId, in ResultReceiver receiver);
+
+ /**
+ * Register for listening to satellite modem state changes.
+ *
+ * @param subId The subId of the subscription to register for satellite modem state changes.
+ * @param callback The callback to handle the satellite modem state changed event.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForSatelliteModemStateChange(int subId, ISatelliteStateListener callback);
+
+ /**
+ * Unregister to stop listening to satellite modem state changes.
+ *
+ * @param subId The subId of the subscription to unregister for satellite modem state changes.
+ * @param callback The callback that was passed to registerForSatelliteStateChange.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int unregisterForSatelliteModemStateChange(int subId, ISatelliteStateListener callback);
+
+ /**
+ * Register to receive incoming datagrams over satellite.
+ *
+ * @param subId The subId of the subscription to register for incoming satellite datagrams.
+ * @param datagramType Type of datagram.
+ * @param callback The callback to handle receiving incoming datagrams.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForSatelliteDatagram(int subId, int datagramType, ISatelliteStateListener callback);
+
+ /**
+ * Unregister to stop receiving incoming datagrams over satellite.
+ *
+ * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
+ * @param callback The callback that was passed to registerForSatelliteDatagram.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int unregisterForSatelliteDatagram(int subId, ISatelliteStateListener callback);
+
+ /**
+ * Poll pending satellite datagrams over satellite.
+ *
+ * @param subId The subId of the subscription to poll pending satellite datagrams for.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int pollPendingSatelliteDatagrams(int subId);
+
+ /**
+ * Send datagram over satellite.
+ *
+ * @param subId The subId of the subscription to send satellite datagrams for.
+ * @param datagramType Type of datagram.
+ * @param datagram Datagram to send over satellite.
+ * @param callback The callback to get the error code of the request.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void sendSatelliteDatagram(int subId, int datagramType, in SatelliteDatagram datagram,
+ IIntegerConsumer callback);
+
+ /**
+ * Request to get whether satellite communication is allowed for the current location.
+ *
+ * @param subId The subId of the subscription to get whether satellite communication is allowed
+ * for the current location for.
+ * @param receiver Result receiver to get the error code of the request and whether satellite
+ * communication is allowed for the current location.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
+ in ResultReceiver receiver);
+
+ /**
+ * Request to get the time after which the satellite will next be visible.
+ *
+ * @param subId The subId to get the time after which the satellite will next be visible for.
+ * @param receiver Result receiver to get the error code of the request and the requested
+ * time after which the satellite will next be visible.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestTimeForNextSatelliteVisibility(int subId, in ResultReceiver receiver);
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
index daff76f..5d4a4ef 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
@@ -61,10 +61,11 @@
return intent;
}
- protected Intent createBroadcastIntent(String action) {
+ protected Intent createFgBroadcastIntent(String action) {
final Intent intent = new Intent(action);
- intent.addFlags(
- Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
putTimeReceiverBinderExtra(intent);
return intent;
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
index bc528d4..6a53575 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
@@ -30,11 +30,11 @@
@LargeTest
public class BroadcastPerfTest extends BasePerfTest {
@Test
- public void manifestBroadcastRunning() {
+ public void manifestFgBroadcastRunning() {
runPerfFunction(() -> {
startTargetPackage();
- final Intent intent = createBroadcastIntent(
+ final Intent intent = createFgBroadcastIntent(
Constants.ACTION_BROADCAST_MANIFEST_RECEIVE);
final long startTime = System.nanoTime();
@@ -48,9 +48,9 @@
}
@Test
- public void manifestBroadcastNotRunning() {
+ public void manifestFgBroadcastNotRunning() {
runPerfFunction(() -> {
- final Intent intent = createBroadcastIntent(
+ final Intent intent = createFgBroadcastIntent(
Constants.ACTION_BROADCAST_MANIFEST_RECEIVE);
final long startTime = System.nanoTime();
@@ -64,11 +64,11 @@
}
@Test
- public void registeredBroadcast() {
+ public void registeredFgBroadcast() {
runPerfFunction(() -> {
startTargetPackage();
- final Intent intent = createBroadcastIntent(
+ final Intent intent = createFgBroadcastIntent(
Constants.ACTION_BROADCAST_REGISTERED_RECEIVE);
final long startTime = System.nanoTime();
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
index 3ea9651..48d050c 100644
--- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
@@ -16,7 +16,9 @@
package android.view.choreographertests;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -63,6 +65,7 @@
private DisplayManager mDisplayManager;
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
+ private Choreographer mChoreographer;
private boolean mIsFirstCallback = true;
private int mCallbackMissedCounter = 0;
@@ -127,37 +130,60 @@
@Test
public void testCreateChoreographer() {
+ Looper testLooper = Looper.myLooper();
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
}
SurfaceControl sc = mSurfaceView.getSurfaceControl();
+ mChoreographer = sc.getChoreographer();
mTestCompleteSignal.countDown();
SurfaceControl sc1 = new SurfaceControl(sc, "AttachedChoreographerTests");
// Create attached choreographer with getChoreographer
- sc1.getChoreographer();
+ Choreographer choreographer1 = sc1.getChoreographer();
assertTrue(sc1.hasChoreographer());
assertTrue(sc1.isValid());
- sc1.release();
+ assertEquals(choreographer1, sc1.getChoreographer());
+ assertEquals(choreographer1, sc1.getChoreographer(Looper.myLooper()));
+ assertEquals(choreographer1, sc1.getChoreographer(Looper.getMainLooper()));
+ assertThrows(IllegalStateException.class, () -> sc1.getChoreographer(testLooper));
SurfaceControl sc2 = new SurfaceControl(sc, "AttachedChoreographerTests");
// Create attached choreographer with Looper.myLooper
- sc2.getChoreographer(Looper.myLooper());
+ Choreographer choreographer2 = sc2.getChoreographer(Looper.myLooper());
assertTrue(sc2.hasChoreographer());
assertTrue(sc2.isValid());
- sc2.release();
+ assertEquals(choreographer2, sc2.getChoreographer(Looper.myLooper()));
+ assertEquals(choreographer2, sc2.getChoreographer(Looper.getMainLooper()));
+ assertEquals(choreographer2, sc2.getChoreographer());
+ assertThrows(IllegalStateException.class, () -> sc2.getChoreographer(testLooper));
SurfaceControl sc3 = new SurfaceControl(sc, "AttachedChoreographerTests");
// Create attached choreographer with Looper.myLooper
- sc3.getChoreographer(Looper.getMainLooper());
+ Choreographer choreographer3 = sc3.getChoreographer(Looper.getMainLooper());
assertTrue(sc3.hasChoreographer());
assertTrue(sc3.isValid());
+ assertEquals(choreographer3, sc3.getChoreographer(Looper.getMainLooper()));
+ assertEquals(choreographer3, sc3.getChoreographer(Looper.myLooper()));
+ assertEquals(choreographer3, sc3.getChoreographer());
+ assertThrows(IllegalStateException.class, () -> sc3.getChoreographer(testLooper));
+
+ assertNotEquals(choreographer1, choreographer2);
+ assertNotEquals(choreographer1, choreographer3);
+ assertNotEquals(choreographer2, choreographer3);
+ sc1.release();
+ sc2.release();
sc3.release();
mTestCompleteSignal.countDown();
});
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
fail("Test not finished in 2 Seconds");
}
+ SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl();
+ assertTrue(surfaceControl.hasChoreographer());
+ assertEquals(mChoreographer, surfaceControl.getChoreographer());
+ assertThrows(IllegalStateException.class,
+ () -> surfaceControl.getChoreographer(testLooper));
}
@Test
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
index 046174c..1bd8f6a 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -20,12 +20,15 @@
import android.content.pm.ActivityInfo
import android.hardware.display.DisplayManager
import android.util.AttributeSet
+import android.util.Log
+import android.view.Display
import android.view.Window
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import com.android.test.silkfx.R
import com.android.test.silkfx.app.WindowObserver
+import java.util.function.Consumer
class ColorModeControls : LinearLayout, WindowObserver {
private val COLOR_MODE_HDR10 = 3
@@ -66,6 +69,38 @@
}
}
+ private val hdrsdrListener = Consumer<Display> { display ->
+ Log.d("SilkFX", "HDR/SDR changed ${display.hdrSdrRatio}")
+ }
+
+ private val displayChangedListener = object : DisplayManager.DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {
+ Log.d("SilkFX", "onDisplayAdded")
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {
+ Log.d("SilkFX", "onDisplayRemoved")
+ }
+
+ override fun onDisplayChanged(displayId: Int) {
+ Log.d("SilkFX", "onDisplayChanged")
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ Log.d("SilkFX", "is hdr/sdr available: ${display.isHdrSdrRatioAvailable}; " +
+ "current ration = ${display.hdrSdrRatio}")
+ display.registerHdrSdrRatioChangedListener({ it.run() }, hdrsdrListener)
+ displayManager.registerDisplayListener(displayChangedListener, handler)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ display.unregisterHdrSdrRatioChangedListener(hdrsdrListener)
+ displayManager.unregisterDisplayListener(displayChangedListener)
+ }
+
private fun setColorMode(newMode: Int) {
val window = window!!
var sdrWhitepointChanged = false
diff --git a/tests/testables/src/com/android/internal/config/sysui/OWNERS b/tests/testables/src/com/android/internal/config/sysui/OWNERS
new file mode 100644
index 0000000..2e96c97
--- /dev/null
+++ b/tests/testables/src/com/android/internal/config/sysui/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
similarity index 68%
rename from services/tests/uiservicestests/src/com/android/server/notification/TestFlagResolver.java
rename to tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
index 3b9726e..a8815dc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestFlagResolver.java
+++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
@@ -14,22 +14,20 @@
* limitations under the License.
*/
-package com.android.server.notification;
-
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+package com.android.internal.config.sysui;
import java.util.HashMap;
import java.util.Map;
-public class TestFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver {
- private Map<SystemUiSystemPropertiesFlags.Flag, Boolean> mOverrides = new HashMap<>();
+public class TestableFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver {
+ private Map<String, Boolean> mOverrides = new HashMap<>();
@Override
public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) {
- return mOverrides.getOrDefault(flag, flag.mDefaultValue);
+ return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue);
}
public void setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) {
- mOverrides.put(flag, isEnabled);
+ mOverrides.put(flag.mSysPropKey, isEnabled);
}
}
diff --git a/tests/testables/tests/com/android/internal/config/sysui/OWNERS b/tests/testables/tests/com/android/internal/config/sysui/OWNERS
new file mode 100644
index 0000000..2e96c97
--- /dev/null
+++ b/tests/testables/tests/com/android/internal/config/sysui/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/OWNERS
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
index 5f6c6f7..7375c16 100644
--- a/tools/lint/fix/Android.bp
+++ b/tools/lint/fix/Android.bp
@@ -25,4 +25,10 @@
name: "lint_fix",
main: "lint_fix.py",
srcs: ["lint_fix.py"],
+ libs: ["soong_lint_fix"],
+}
+
+python_library_host {
+ name: "soong_lint_fix",
+ srcs: ["soong_lint_fix.py"],
}
diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py
index 0f94bd2..1c83f7b 100644
--- a/tools/lint/fix/lint_fix.py
+++ b/tools/lint/fix/lint_fix.py
@@ -1,77 +1,29 @@
-import argparse
-import os
-import sys
+# 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.
+#
+# 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.
-ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
-PATH_PREFIX = "out/soong/.intermediates"
-PATH_SUFFIX = "android_common/lint"
-FIX_DIR = "suggested-fixes"
+from soong_lint_fix import SoongLintFix
-parser = argparse.ArgumentParser(description="""
-This is a python script that applies lint fixes to the platform:
-1. Set up the environment, etc.
-2. Build the lint and run it.
-3. Unpack soong's intermediate zip containing source files modified by lint.
-4. Copy the modified files back into the tree.
-
-**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` \
-so that the `ANDROID_BUILD_TOP` environment variable has been set.
-Alternatively, set it manually in your shell.
-""", formatter_class=argparse.RawTextHelpFormatter)
-
-parser.add_argument('build_path', metavar='build_path', type=str,
- help='The build module to run '
- '(e.g. frameworks/base/framework-minus-apex or '
- 'frameworks/base/services/core/services.core.unboosted)')
-
-parser.add_argument('--check', metavar='check', type=str,
- help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.')
-
-parser.add_argument('--dry-run', dest='dry_run', action='store_true',
- help='Just print the resulting shell script instead of running it.')
-
-parser.add_argument('--no-fix', dest='no_fix', action='store_true',
- help='Just build and run the lint, do NOT apply the fixes.')
-
-args = parser.parse_args()
-
-path = f"{PATH_PREFIX}/{args.build_path}/{PATH_SUFFIX}"
-target = f"{path}/lint-report.html"
-
-commands = []
-
-if not args.dry_run:
- commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"]
-
-if args.check:
- commands += [f"export ANDROID_LINT_CHECK={args.check}"]
-
-commands += [
- "cd $ANDROID_BUILD_TOP",
- "source build/envsetup.sh",
- f"rm {target}", # remove the file first so soong doesn't think there is no work to do
- f"rm {path}/{FIX_DIR}.zip", # remove in case there are fixes from a prior run that we don't want applied if this run fails
- f"m {target}",
-]
-
-if not args.no_fix:
- commands += [
- f"cd {path}",
- f"unzip {FIX_DIR}.zip -d {FIX_DIR}",
- f"cd {FIX_DIR}",
- # Find all the java files in the fix directory, excluding the ./out subdirectory,
- # and copy them back into the same path within the tree.
- f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --",
- f"rm -rf {FIX_DIR}"
- ]
-
-if args.dry_run:
- print("(\n" + ";\n".join(commands) + "\n)")
- sys.exit(0)
-
-with_echo = []
-for c in commands:
- with_echo.append(f'echo "{c}"')
- with_echo.append(c)
-
-os.system("(\n" + ";\n".join(with_echo) + "\n)")
+SoongLintFix().run()
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
new file mode 100644
index 0000000..3308df6
--- /dev/null
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -0,0 +1,169 @@
+# 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.
+
+import argparse
+import os
+import subprocess
+import sys
+
+ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+PATH_PREFIX = "out/soong/.intermediates"
+PATH_SUFFIX = "android_common/lint"
+FIX_DIR = "suggested-fixes"
+
+class SoongLintFix:
+ """
+ This class creates a command line tool that will
+ apply lint fixes to the platform via the necessary
+ combination of soong and shell commands.
+
+ It provides some basic hooks for experimental code
+ to tweak the generation of the resulting shell script.
+
+ By default, it will apply lint fixes using the intermediate `suggested-fixes`
+ directory that soong creates during its invocation of lint.
+
+ The default argument parser configures a number of command line arguments to
+ facilitate running lint via soong.
+
+ Basic usage:
+ ```
+ from soong_lint_fix import SoongLintFix
+
+ SoongLintFix().run()
+ ```
+ """
+ def __init__(self):
+ self._commands = None
+ self._args = None
+ self._path = None
+ self._target = None
+ self._parser = _setup_parser()
+
+
+ def add_argument(self, *args, **kwargs):
+ """
+ If necessary, add arguments to the underlying argparse.ArgumentParser before running
+ """
+ self._parser.add_argument(*args, **kwargs)
+
+
+ def run(self, add_setup_commands=None, override_fix_commands=None):
+ """
+ Run the script
+ :param add_setup_commands: OPTIONAL function to add additional setup commands
+ passed the command line arguments, path, and build target
+ must return a list of strings (the additional commands)
+ :param override_fix_commands: OPTIONAL function to override the fix commands
+ passed the command line arguments, path, and build target
+ must return a list of strings (the fix commands)
+ """
+ self._setup()
+ if add_setup_commands:
+ self._commands += add_setup_commands(self._args, self._path, self._target)
+
+ self._add_lint_report_commands()
+
+ if not self._args.no_fix:
+ if override_fix_commands:
+ self._commands += override_fix_commands(self._args, self._path, self._target)
+ else:
+ self._commands += [
+ f"cd {self._path}",
+ f"unzip {FIX_DIR}.zip -d {FIX_DIR}",
+ f"cd {FIX_DIR}",
+ # Find all the java files in the fix directory, excluding the ./out subdirectory,
+ # and copy them back into the same path within the tree.
+ f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1 || exit 255' --",
+ f"rm -rf {FIX_DIR}"
+ ]
+
+
+ if self._args.dry_run:
+ print(self._get_commands_str())
+ else:
+ self._execute()
+
+
+ def _setup(self):
+ self._args = self._parser.parse_args()
+ self._commands = []
+ self._path = f"{PATH_PREFIX}/{self._args.build_path}/{PATH_SUFFIX}"
+ self._target = f"{self._path}/lint-report.html"
+
+ if not self._args.dry_run:
+ self._commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"]
+
+ if self._args.check:
+ self._commands += [f"export ANDROID_LINT_CHECK={self._args.check}"]
+
+
+ def _add_lint_report_commands(self):
+ self._commands += [
+ "cd $ANDROID_BUILD_TOP",
+ "source build/envsetup.sh",
+ # remove the file first so soong doesn't think there is no work to do
+ f"rm {self._target}",
+ # remove in case there are fixes from a prior run,
+ # that we don't want applied if this run fails
+ f"rm {self._path}/{FIX_DIR}.zip",
+ f"m {self._target}",
+ ]
+
+
+ def _get_commands_str(self):
+ prefix = "(\n"
+ delimiter = ";\n"
+ suffix = "\n)"
+ return f"{prefix}{delimiter.join(self._commands)}{suffix}"
+
+
+ def _execute(self, with_echo=True):
+ if with_echo:
+ exec_commands = []
+ for c in self._commands:
+ exec_commands.append(f'echo "{c}"')
+ exec_commands.append(c)
+ self._commands = exec_commands
+
+ subprocess.call(self._get_commands_str(), executable='/bin/bash', shell=True)
+
+
+def _setup_parser():
+ parser = argparse.ArgumentParser(description="""
+ This is a python script that applies lint fixes to the platform:
+ 1. Set up the environment, etc.
+ 2. Run lint on the specified target.
+ 3. Copy the modified files, from soong's intermediate directory, back into the tree.
+
+ **Gotcha**: You must have run `source build/envsetup.sh` and `lunch`
+ so that the `ANDROID_BUILD_TOP` environment variable has been set.
+ Alternatively, set it manually in your shell.
+ """, formatter_class=argparse.RawTextHelpFormatter)
+
+ parser.add_argument('build_path', metavar='build_path', type=str,
+ help='The build module to run '
+ '(e.g. frameworks/base/framework-minus-apex or '
+ 'frameworks/base/services/core/services.core.unboosted)')
+
+ parser.add_argument('--check', metavar='check', type=str,
+ help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.')
+
+ parser.add_argument('--dry-run', dest='dry_run', action='store_true',
+ help='Just print the resulting shell script instead of running it.')
+
+ parser.add_argument('--no-fix', dest='no_fix', action='store_true',
+ help='Just build and run the lint, do NOT apply the fixes.')
+
+ return parser
diff --git a/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java b/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java
index 00ebe62..2f15066 100644
--- a/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java
+++ b/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java
@@ -19,9 +19,12 @@
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.RequiresApi;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -38,6 +41,8 @@
private int mMin2gRssi;
private int mMin5gRssi;
private int mMin6gRssi;
+ private int mScanIterations;
+ private int mScanIntervalMultiplier;
private List<PnoNetwork> mPnoNetworks;
/** Construct an uninitialized PnoSettings object */
@@ -122,6 +127,46 @@
}
/**
+ * Get the requested PNO scan iterations.
+ *
+ * @return PNO scan iterations.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public int getScanIterations() {
+ return mScanIterations;
+ }
+
+ /**
+ * Set the requested PNO scan iterations.
+ *
+ * @param scanIterations the PNO scan iterations.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void setScanIterations(int scanIterations) {
+ this.mScanIterations = scanIterations;
+ }
+
+ /**
+ * Get the requested PNO scan interval multiplier.
+ *
+ * @return PNO scan interval multiplier.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public int getScanIntervalMultiplier() {
+ return mScanIntervalMultiplier;
+ }
+
+ /**
+ * Set the requested PNO scan interval multiplier.
+ *
+ * @param scanIntervalMultiplier the PNO scan interval multiplier.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void setScanIntervalMultiplier(int scanIntervalMultiplier) {
+ this.mScanIntervalMultiplier = scanIntervalMultiplier;
+ }
+
+ /**
* Return the configured list of specific networks to search for in a PNO scan.
*
* @return A list of {@link PnoNetwork} objects, possibly empty if non configured.
@@ -156,13 +201,16 @@
&& mMin2gRssi == settings.mMin2gRssi
&& mMin5gRssi == settings.mMin5gRssi
&& mMin6gRssi == settings.mMin6gRssi
+ && mScanIterations == settings.mScanIterations
+ && mScanIntervalMultiplier == settings.mScanIntervalMultiplier
&& mPnoNetworks.equals(settings.mPnoNetworks);
}
/** override hash code */
@Override
public int hashCode() {
- return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi, mPnoNetworks);
+ return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi,
+ mScanIterations, mScanIntervalMultiplier, mPnoNetworks);
}
/** implement Parcelable interface */
@@ -181,6 +229,8 @@
out.writeInt(mMin2gRssi);
out.writeInt(mMin5gRssi);
out.writeInt(mMin6gRssi);
+ out.writeInt(mScanIterations);
+ out.writeInt(mScanIntervalMultiplier);
out.writeTypedList(mPnoNetworks);
}
@@ -194,6 +244,8 @@
result.mMin2gRssi = in.readInt();
result.mMin5gRssi = in.readInt();
result.mMin6gRssi = in.readInt();
+ result.mScanIterations = in.readInt();
+ result.mScanIntervalMultiplier = in.readInt();
result.mPnoNetworks = new ArrayList<>();
in.readTypedList(result.mPnoNetworks, PnoNetwork.CREATOR);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java
index 9aad9aa..52abf33 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java
@@ -270,12 +270,22 @@
return 0;
}
+ /**
+ * Creates a {@link DeviceInfo} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static DeviceInfo readFromParcel(@NonNull Parcel in) {
+ return new DeviceInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
+ in.readInt());
+ }
+
@NonNull
public static final Creator<DeviceInfo> CREATOR = new Creator<DeviceInfo>() {
@Override
public DeviceInfo createFromParcel(Parcel in) {
- return new DeviceInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
- in.readInt());
+ return readFromParcel(in);
}
@Override
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
index b219e86..161c83c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -228,15 +228,25 @@
dest.writeInt(mNetworkSource);
dest.writeString(mSsid);
dest.writeIntArray(mSecurityTypes);
- dest.writeTypedObject(mDeviceInfo, 0);
+ mDeviceInfo.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Creates a {@link KnownNetwork} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static KnownNetwork readFromParcel(@NonNull Parcel in) {
+ return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(),
+ DeviceInfo.readFromParcel(in));
}
@NonNull
public static final Creator<KnownNetwork> CREATOR = new Creator<>() {
@Override
public KnownNetwork createFromParcel(Parcel in) {
- return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(),
- in.readTypedObject(DeviceInfo.CREATOR));
+ return readFromParcel(in);
}
@Override
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
index 2cefb8e..b2f04ff 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
@@ -63,6 +63,7 @@
public @interface ConnectionStatus {}
@ConnectionStatus private final int mStatus;
+ private final KnownNetwork mKnownNetwork;
private final Bundle mExtras;
/**
@@ -70,6 +71,7 @@
*/
public static final class Builder {
@ConnectionStatus private int mStatus;
+ private KnownNetwork mKnownNetwork;
private Bundle mExtras;
public Builder() {}
@@ -86,6 +88,17 @@
}
/**
+ * Sets the {@link KnownNetwork} object of the connection.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setKnownNetwork(@NonNull KnownNetwork knownNetwork) {
+ mKnownNetwork = knownNetwork;
+ return this;
+ }
+
+ /**
* Sets the extras bundle
*
* @return Returns the Builder object.
@@ -103,12 +116,14 @@
*/
@NonNull
public KnownNetworkConnectionStatus build() {
- return new KnownNetworkConnectionStatus(mStatus, mExtras);
+ return new KnownNetworkConnectionStatus(mStatus, mKnownNetwork, mExtras);
}
}
- private KnownNetworkConnectionStatus(@ConnectionStatus int status, Bundle extras) {
+ private KnownNetworkConnectionStatus(@ConnectionStatus int status, KnownNetwork knownNetwork,
+ Bundle extras) {
mStatus = status;
+ mKnownNetwork = knownNetwork;
mExtras = extras;
}
@@ -123,6 +138,16 @@
}
/**
+ * Gets the {@link KnownNetwork} object of the connection.
+ *
+ * @return Returns a KnownNetwork object.
+ */
+ @NonNull
+ public KnownNetwork getKnownNetwork() {
+ return mKnownNetwork;
+ }
+
+ /**
* Gets the extras Bundle.
*
* @return Returns a Bundle object.
@@ -136,12 +161,13 @@
public boolean equals(Object obj) {
if (!(obj instanceof KnownNetworkConnectionStatus)) return false;
KnownNetworkConnectionStatus other = (KnownNetworkConnectionStatus) obj;
- return mStatus == other.getStatus();
+ return mStatus == other.getStatus()
+ && Objects.equals(mKnownNetwork, other.getKnownNetwork());
}
@Override
public int hashCode() {
- return Objects.hash(mStatus);
+ return Objects.hash(mStatus, mKnownNetwork);
}
@Override
@@ -152,15 +178,27 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mStatus);
+ mKnownNetwork.writeToParcel(dest, flags);
dest.writeBundle(mExtras);
}
+ /**
+ * Creates a {@link KnownNetworkConnectionStatus} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static KnownNetworkConnectionStatus readFromParcel(@NonNull Parcel in) {
+ return new KnownNetworkConnectionStatus(in.readInt(),
+ KnownNetwork.readFromParcel(in),
+ in.readBundle());
+ }
+
@NonNull
public static final Creator<KnownNetworkConnectionStatus> CREATOR = new Creator<>() {
@Override
public KnownNetworkConnectionStatus createFromParcel(Parcel in) {
- return new KnownNetworkConnectionStatus(in.readInt(),
- in.readBundle(getClass().getClassLoader()));
+ return readFromParcel(in);
}
@Override
@@ -173,6 +211,7 @@
public String toString() {
return new StringBuilder("KnownNetworkConnectionStatus[")
.append("status=").append(mStatus)
+ .append("known network=").append(mKnownNetwork.toString())
.append("extras=").append(mExtras.toString())
.append("]").toString();
}
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 c298189..8aa369e 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -35,6 +35,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.R;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -51,8 +53,6 @@
public class SharedConnectivityManager {
private static final String TAG = SharedConnectivityManager.class.getSimpleName();
private static final boolean DEBUG = true;
- private static final String SERVICE_PACKAGE_NAME = "sharedconnectivity_service_package";
- private static final String SERVICE_CLASS_NAME = "sharedconnectivity_service_class";
private static final class SharedConnectivityCallbackProxy extends
ISharedConnectivityCallback.Stub {
@@ -101,7 +101,7 @@
Binder.restoreCallingIdentity(token);
}
}
- };
+ }
@Override
public void onTetherNetworkConnectionStatusChanged(
@@ -115,7 +115,7 @@
Binder.restoreCallingIdentity(token);
}
}
- };
+ }
@Override
public void onKnownNetworkConnectionStatusChanged(
@@ -129,7 +129,7 @@
Binder.restoreCallingIdentity(token);
}
}
- };
+ }
}
private ISharedConnectivityService mService;
@@ -137,14 +137,33 @@
mProxyMap = new HashMap<>();
/**
- * Constructor for new instance of {@link SharedConnectivityManager}.
+ * Creates a new instance of {@link SharedConnectivityManager}.
*
* Automatically binds to implementation of {@link SharedConnectivityService} specified in
* device overlay.
*
+ * @return An instance of {@link SharedConnectivityManager} or null if the shared connectivity
+ * service is not found.
* @hide
*/
- public SharedConnectivityManager(@NonNull Context context) {
+ @Nullable
+ public static SharedConnectivityManager create(@NonNull Context context) {
+ Resources resources = context.getResources();
+ try {
+ String servicePackageName = resources.getString(
+ R.string.shared_connectivity_service_package);
+ String serviceIntentAction = resources.getString(
+ R.string.shared_connectivity_service_intent_action);
+ return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "To support shared connectivity service on this device, the service's"
+ + " package name and intent action string must be defined");
+ }
+ return null;
+ }
+
+ private SharedConnectivityManager(@NonNull Context context, String servicePackageName,
+ String serviceIntentAction) {
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -158,7 +177,10 @@
mProxyMap.clear();
}
};
- bind(context, serviceConnection);
+
+ context.bindService(
+ new Intent().setPackage(servicePackageName).setAction(serviceIntentAction),
+ serviceConnection, Context.BIND_AUTO_CREATE);
}
/**
@@ -169,25 +191,6 @@
mService = (ISharedConnectivityService) service;
}
- private void bind(Context context, ServiceConnection serviceConnection) {
- Resources resources = context.getResources();
- int packageNameId = resources.getIdentifier(SERVICE_PACKAGE_NAME, "string",
- context.getPackageName());
- int classNameId = resources.getIdentifier(SERVICE_CLASS_NAME, "string",
- context.getPackageName());
- if (packageNameId == 0 || classNameId == 0) {
- throw new Resources.NotFoundException("Package and class names for"
- + " shared connectivity service must be defined");
- }
-
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(resources.getString(packageNameId),
- resources.getString(classNameId)));
- context.bindService(
- intent,
- serviceConnection, Context.BIND_AUTO_CREATE);
- }
-
/**
* Registers a callback for receiving updates to the list of Tether Networks and Known Networks.
*
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 87f5efd..4809bef 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -128,12 +128,22 @@
dest.writeBundle(mExtras);
}
+ /**
+ * Creates a {@link SharedConnectivitySettingsState} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) {
+ return new SharedConnectivitySettingsState(in.readBoolean(),
+ in.readBundle());
+ }
+
@NonNull
public static final Creator<SharedConnectivitySettingsState> CREATOR = new Creator<>() {
@Override
public SharedConnectivitySettingsState createFromParcel(Parcel in) {
- return new SharedConnectivitySettingsState(in.readBoolean(),
- in.readBundle(getClass().getClassLoader()));
+ return readFromParcel(in);
}
@Override
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
index 3eff724..af4fd4a 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
@@ -328,7 +328,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mDeviceId);
- dest.writeTypedObject(mDeviceInfo, 0);
+ mDeviceInfo.writeToParcel(dest, flags);
dest.writeInt(mNetworkType);
dest.writeString(mNetworkName);
dest.writeString(mHotspotSsid);
@@ -336,14 +336,23 @@
dest.writeIntArray(mHotspotSecurityTypes);
}
+ /**
+ * Creates a {@link TetherNetwork} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static TetherNetwork readFromParcel(@NonNull Parcel in) {
+ return new TetherNetwork(in.readLong(), DeviceInfo.readFromParcel(in),
+ in.readInt(), in.readString(), in.readString(), in.readString(),
+ in.createIntArray());
+ }
+
@NonNull
public static final Creator<TetherNetwork> CREATOR = new Creator<>() {
@Override
public TetherNetwork createFromParcel(Parcel in) {
- return new TetherNetwork(in.readLong(), in.readTypedObject(
- android.net.wifi.sharedconnectivity.app.DeviceInfo.CREATOR),
- in.readInt(), in.readString(), in.readString(), in.readString(),
- in.createIntArray());
+ return readFromParcel(in);
}
@Override
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatus.java
index 86202bb..3cf44ed 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatus.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatus.java
@@ -105,6 +105,7 @@
public @interface ConnectionStatus {}
@ConnectionStatus private final int mStatus;
+ private final TetherNetwork mTetherNetwork;
private final Bundle mExtras;
/**
@@ -112,6 +113,7 @@
*/
public static final class Builder {
@ConnectionStatus private int mStatus;
+ private TetherNetwork mTetherNetwork;
private Bundle mExtras;
public Builder() {}
@@ -128,6 +130,17 @@
}
/**
+ * Sets the {@link TetherNetwork} object of the connection.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setTetherNetwork(@NonNull TetherNetwork tetherNetwork) {
+ mTetherNetwork = tetherNetwork;
+ return this;
+ }
+
+ /**
* Sets the extras bundle
*
* @return Returns the Builder object.
@@ -145,12 +158,14 @@
*/
@NonNull
public TetherNetworkConnectionStatus build() {
- return new TetherNetworkConnectionStatus(mStatus, mExtras);
+ return new TetherNetworkConnectionStatus(mStatus, mTetherNetwork, mExtras);
}
}
- private TetherNetworkConnectionStatus(@ConnectionStatus int status, Bundle extras) {
+ private TetherNetworkConnectionStatus(@ConnectionStatus int status, TetherNetwork tetherNetwork,
+ Bundle extras) {
mStatus = status;
+ mTetherNetwork = tetherNetwork;
mExtras = extras;
}
@@ -165,6 +180,16 @@
}
/**
+ * Gets the {@link TetherNetwork} object of the connection.
+ *
+ * @return Returns a TetherNetwork object.
+ */
+ @NonNull
+ public TetherNetwork getTetherNetwork() {
+ return mTetherNetwork;
+ }
+
+ /**
* Gets the extras Bundle.
*
* @return Returns a Bundle object.
@@ -178,12 +203,13 @@
public boolean equals(Object obj) {
if (!(obj instanceof TetherNetworkConnectionStatus)) return false;
TetherNetworkConnectionStatus other = (TetherNetworkConnectionStatus) obj;
- return mStatus == other.getStatus();
+ return mStatus == other.getStatus()
+ && Objects.equals(mTetherNetwork, other.getTetherNetwork());
}
@Override
public int hashCode() {
- return Objects.hash(mStatus);
+ return Objects.hash(mStatus, mTetherNetwork);
}
@Override
@@ -194,27 +220,39 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mStatus);
+ mTetherNetwork.writeToParcel(dest, flags);
dest.writeBundle(mExtras);
}
+ /**
+ * Creates a {@link TetherNetworkConnectionStatus} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static TetherNetworkConnectionStatus readFromParcel(@NonNull Parcel in) {
+ return new TetherNetworkConnectionStatus(in.readInt(),
+ TetherNetwork.readFromParcel(in), in.readBundle());
+ }
+
@NonNull
public static final Creator<TetherNetworkConnectionStatus> CREATOR = new Creator<>() {
- @Override
- public TetherNetworkConnectionStatus createFromParcel(Parcel in) {
- return new TetherNetworkConnectionStatus(in.readInt(),
- in.readBundle(getClass().getClassLoader()));
- }
+ @Override
+ public TetherNetworkConnectionStatus createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
- @Override
- public TetherNetworkConnectionStatus[] newArray(int size) {
- return new TetherNetworkConnectionStatus[size];
- }
- };
+ @Override
+ public TetherNetworkConnectionStatus[] newArray(int size) {
+ return new TetherNetworkConnectionStatus[size];
+ }
+ };
@Override
public String toString() {
return new StringBuilder("TetherNetworkConnectionStatus[")
.append("status=").append(mStatus)
+ .append("tether network=").append(mTetherNetwork.toString())
.append("extras=").append(mExtras.toString())
.append("]").toString();
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index a40049b..10ef066 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -101,13 +101,13 @@
@Override
public void registerCallback(ISharedConnectivityCallback callback) {
checkPermissions();
- mHandler.post(() -> registerCallback(callback));
+ mHandler.post(() -> onRegisterCallback(callback));
}
@Override
public void unregisterCallback(ISharedConnectivityCallback callback) {
checkPermissions();
- mHandler.post(() -> unregisterCallback(callback));
+ mHandler.post(() -> onUnregisterCallback(callback));
}
@Override
@@ -147,7 +147,7 @@
};
}
- private void registerCallback(ISharedConnectivityCallback callback) {
+ private void onRegisterCallback(ISharedConnectivityCallback callback) {
// Listener gets triggered on first register using cashed data
if (!notifyTetherNetworkUpdate(callback) || !notifyKnownNetworkUpdate(callback)
|| !notifySettingsStateUpdate(callback)
@@ -167,7 +167,7 @@
}
}
- private void unregisterCallback(ISharedConnectivityCallback callback) {
+ private void onUnregisterCallback(ISharedConnectivityCallback callback) {
DeathRecipient deathRecipient = mDeathRecipientMap.get(callback);
if (deathRecipient != null) {
callback.asBinder().unlinkToDeath(deathRecipient, 0);
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
new file mode 100644
index 0000000..37dca8d
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET;
+import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
+import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED;
+import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVE_FAILED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus}.
+ */
+@SmallTest
+public class KnownNetworkConnectionStatusTest {
+ private static final int NETWORK_SOURCE = NETWORK_SOURCE_NEARBY_SELF;
+ private static final String SSID = "TEST_SSID";
+ private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP};
+ private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
+ .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+ .setConnectionStrength(2).setBatteryPercentage(50).build();
+ private static final String SSID_1 = "TEST_SSID1";
+ private static final String BUNDLE_KEY = "INT-KEY";
+
+ /**
+ * Verifies parcel serialization/deserialization.
+ */
+ @Test
+ public void testParcelOperation() {
+ KnownNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
+
+ Parcel parcelW = Parcel.obtain();
+ status.writeToParcel(parcelW, 0);
+ byte[] bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ Parcel parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ KnownNetworkConnectionStatus fromParcel =
+ KnownNetworkConnectionStatus.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(status, fromParcel);
+ assertEquals(status.hashCode(), fromParcel.hashCode());
+ }
+
+ /**
+ * Verifies the Equals operation
+ */
+ @Test
+ public void testEqualsOperation() {
+ KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
+ KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
+ assertEquals(status2, status2);
+
+ KnownNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder()
+ .setStatus(CONNECTION_STATUS_SAVE_FAILED);
+ assertNotEquals(status1, builder.build());
+
+ builder = buildConnectionStatusBuilder()
+ .setKnownNetwork(buildKnownNetworkBuilder().setSsid(SSID_1).build());
+ assertNotEquals(status1, builder.build());
+ }
+
+ /**
+ * Verifies the get methods return the expected data.
+ */
+ @Test
+ public void testGetMethods() {
+ KnownNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
+ assertEquals(status.getStatus(), CONNECTION_STATUS_SAVED);
+ assertEquals(status.getKnownNetwork(), buildKnownNetworkBuilder().build());
+ assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY));
+ }
+
+ private KnownNetworkConnectionStatus.Builder buildConnectionStatusBuilder() {
+ return new KnownNetworkConnectionStatus.Builder()
+ .setStatus(CONNECTION_STATUS_SAVED)
+ .setKnownNetwork(buildKnownNetworkBuilder().build())
+ .setExtras(buildBundle());
+ }
+
+ private Bundle buildBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(BUNDLE_KEY, 1);
+ return bundle;
+ }
+
+ private KnownNetwork.Builder buildKnownNetworkBuilder() {
+ return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
+ .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO);
+ }
+
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 9aeccac..784e9c4 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -89,7 +89,7 @@
*/
@Test
public void testBindingToService() {
- new SharedConnectivityManager(mContext);
+ SharedConnectivityManager.create(mContext);
verify(mContext).bindService(any(), any(), anyInt());
}
@@ -98,22 +98,22 @@
*/
@Test
public void testRegisterCallback() throws Exception {
- SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
assertFalse(manager.registerCallback(mExecutor, mClientCallback));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
assertTrue(manager.registerCallback(mExecutor, mClientCallback));
verify(mService).registerCallback(any());
// Registering the same callback twice should fail.
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.registerCallback(mExecutor, mClientCallback);
assertFalse(manager.registerCallback(mExecutor, mClientCallback));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
doThrow(new RemoteException()).when(mService).registerCallback(any());
assertFalse(manager.registerCallback(mExecutor, mClientCallback));
@@ -125,24 +125,24 @@
*/
@Test
public void testUnregisterCallback() throws Exception {
- SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
assertFalse(manager.unregisterCallback(mClientCallback));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.registerCallback(mExecutor, mClientCallback);
assertTrue(manager.unregisterCallback(mClientCallback));
verify(mService).unregisterCallback(any());
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.registerCallback(mExecutor, mClientCallback);
manager.unregisterCallback(mClientCallback);
assertFalse(manager.unregisterCallback(mClientCallback));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
doThrow(new RemoteException()).when(mService).unregisterCallback(any());
assertFalse(manager.unregisterCallback(mClientCallback));
@@ -156,11 +156,11 @@
public void testConnectTetherNetwork() throws RemoteException {
TetherNetwork network = buildTetherNetwork();
- SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
assertFalse(manager.connectTetherNetwork(network));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.connectTetherNetwork(network);
verify(mService).connectTetherNetwork(network);
@@ -175,11 +175,11 @@
*/
@Test
public void testDisconnectTetherNetwork() throws RemoteException {
- SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
assertFalse(manager.disconnectTetherNetwork());
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.disconnectTetherNetwork();
verify(mService).disconnectTetherNetwork();
@@ -196,11 +196,11 @@
public void testConnectKnownNetwork() throws RemoteException {
KnownNetwork network = buildKnownNetwork();
- SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
assertFalse(manager.connectKnownNetwork(network));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.connectKnownNetwork(network);
verify(mService).connectKnownNetwork(network);
@@ -217,11 +217,11 @@
public void testForgetKnownNetwork() throws RemoteException {
KnownNetwork network = buildKnownNetwork();
- SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
assertFalse(manager.forgetKnownNetwork(network));
- manager = new SharedConnectivityManager(mContext);
+ manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
manager.forgetKnownNetwork(network);
verify(mService).forgetKnownNetwork(network);
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
new file mode 100644
index 0000000..1d9c2e6
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET;
+import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
+import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT;
+import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus}.
+ */
+@SmallTest
+public class TetherNetworkConnectionStatusTest {
+ private static final long DEVICE_ID = 11L;
+ private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
+ .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+ .setConnectionStrength(2).setBatteryPercentage(50).build();
+ private static final int NETWORK_TYPE = NETWORK_TYPE_CELLULAR;
+ private static final String NETWORK_NAME = "TEST_NETWORK";
+ private static final String HOTSPOT_SSID = "TEST_SSID";
+ private static final String HOTSPOT_BSSID = "TEST _BSSID";
+ private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
+ private static final long DEVICE_ID_1 = 111L;
+ private static final String BUNDLE_KEY = "INT-KEY";
+
+ /**
+ * Verifies parcel serialization/deserialization.
+ */
+ @Test
+ public void testParcelOperation() {
+ TetherNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
+
+ Parcel parcelW = Parcel.obtain();
+ status.writeToParcel(parcelW, 0);
+ byte[] bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ Parcel parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ TetherNetworkConnectionStatus fromParcel =
+ TetherNetworkConnectionStatus.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(status, fromParcel);
+ assertEquals(status.hashCode(), fromParcel.hashCode());
+ }
+
+ /**
+ * Verifies the Equals operation
+ */
+ @Test
+ public void testEqualsOperation() {
+ TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
+ TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
+ assertEquals(status2, status2);
+
+ TetherNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder()
+ .setStatus(CONNECTION_STATUS_TETHERING_TIMEOUT);
+ assertNotEquals(status1, builder.build());
+
+ builder = buildConnectionStatusBuilder()
+ .setTetherNetwork(buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1).build());
+ assertNotEquals(status1, builder.build());
+ }
+
+ /**
+ * Verifies the get methods return the expected data.
+ */
+ @Test
+ public void testGetMethods() {
+ TetherNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
+ assertEquals(status.getStatus(), CONNECTION_STATUS_ENABLING_HOTSPOT);
+ assertEquals(status.getTetherNetwork(), buildTetherNetworkBuilder().build());
+ assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY));
+ }
+
+ private TetherNetworkConnectionStatus.Builder buildConnectionStatusBuilder() {
+
+ return new TetherNetworkConnectionStatus.Builder()
+ .setStatus(CONNECTION_STATUS_ENABLING_HOTSPOT)
+ .setTetherNetwork(buildTetherNetworkBuilder().build())
+ .setExtras(buildBundle());
+ }
+
+ private Bundle buildBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(BUNDLE_KEY, 1);
+ return bundle;
+ }
+
+ private TetherNetwork.Builder buildTetherNetworkBuilder() {
+ return new TetherNetwork.Builder()
+ .setDeviceId(DEVICE_ID)
+ .setDeviceInfo(DEVICE_INFO)
+ .setNetworkType(NETWORK_TYPE)
+ .setNetworkName(NETWORK_NAME)
+ .setHotspotSsid(HOTSPOT_SSID)
+ .setHotspotBssid(HOTSPOT_BSSID)
+ .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES);
+ }
+}